ClipReader was moved to a separate namespace and different files.

This commit is contained in:
John Preston 2016-06-24 13:37:29 +03:00
parent 8fc38d9ac2
commit d64892584d
24 changed files with 1877 additions and 1503 deletions

View file

@ -2462,11 +2462,11 @@ namespace {
return ::sharedContactItems;
}
void regGifItem(ClipReader *reader, HistoryItem *item) {
void regGifItem(Media::Clip::Reader *reader, HistoryItem *item) {
::gifItems.insert(reader, item);
}
void unregGifItem(ClipReader *reader) {
void unregGifItem(Media::Clip::Reader *reader) {
::gifItems.remove(reader);
}

View file

@ -39,7 +39,7 @@ typedef QHash<PhotoData*, HistoryItemsMap> PhotoItems;
typedef QHash<DocumentData*, HistoryItemsMap> DocumentItems;
typedef QHash<WebPageData*, HistoryItemsMap> WebPageItems;
typedef QHash<int32, HistoryItemsMap> SharedContactItems;
typedef QHash<ClipReader*, HistoryItem*> GifItems;
typedef QHash<Media::Clip::Reader*, HistoryItem*> GifItems;
typedef QHash<PhotoId, PhotoData*> PhotosData;
typedef QHash<DocumentId, DocumentData*> DocumentsData;
@ -257,8 +257,8 @@ namespace App {
const SharedContactItems &sharedContactItems();
QString phoneFromSharedContact(int32 userId);
void regGifItem(ClipReader *reader, HistoryItem *item);
void unregGifItem(ClipReader *reader);
void regGifItem(Media::Clip::Reader *reader, HistoryItem *item);
void unregGifItem(Media::Clip::Reader *reader);
void stopGifItems();
void regMuted(PeerData *peer, int32 changeIn);

View file

@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "styles/style_dialogs.h"
#include "history/history_service_layout.h"
#include "data/data_drafts.h"
#include "media/media_clip_reader.h"
#include "lang.h"
#include "mainwidget.h"
#include "application.h"
@ -2906,15 +2907,17 @@ void HistoryItem::setUnreadBarFreezed() {
}
}
void HistoryItem::clipCallback(ClipReaderNotification notification) {
void HistoryItem::clipCallback(Media::Clip::Notification notification) {
using namespace Media::Clip;
HistoryMedia *media = getMedia();
if (!media) return;
ClipReader *reader = media ? media->getClipReader() : 0;
Reader *reader = media ? media->getClipReader() : 0;
if (!reader) return;
switch (notification) {
case ClipReaderReinit: {
case NotificationReinit: {
bool stopped = false;
if (reader->paused()) {
if (MainWidget *m = App::main()) {
@ -2933,7 +2936,7 @@ void HistoryItem::clipCallback(ClipReaderNotification notification) {
}
} break;
case ClipReaderRepaint: {
case NotificationRepaint: {
if (!reader->currentDisplayed()) {
Ui::repaintHistoryItem(this);
}
@ -4526,13 +4529,13 @@ void HistoryGif::initDimensions() {
bool bubble = _parent->hasBubble();
int32 tw = 0, th = 0;
if (gif() && _gif->state() == ClipError) {
if (gif() && _gif->state() == Media::Clip::State::Error) {
if (!_gif->autoplay()) {
Ui::showLayer(new InformBox(lang(lng_gif_error)));
}
App::unregGifItem(_gif);
delete _gif;
_gif = BadClipReader;
_gif = Media::Clip::BadReader;
}
if (gif() && _gif->ready()) {
@ -4637,7 +4640,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6
bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading();
bool selected = (selection == FullSelection);
if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) {
if (loaded && !gif() && _gif != Media::Clip::BadReader && cAutoPlayGif()) {
Ui::autoplayMediaInlineAsync(_parent->fullId());
}
@ -4684,7 +4687,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6
App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayCorners);
}
if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == BadClipReader)) {
if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == Media::Clip::BadReader)) {
float64 radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1;
QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen);
@ -4848,7 +4851,7 @@ bool HistoryGif::playInline(bool autoplay) {
if (!cAutoPlayGif()) {
App::stopGifItems();
}
_gif = new ClipReader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback));
_gif = new Media::Clip::Reader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback));
App::regGifItem(_gif, _parent);
if (gif()) _gif->setAutoplay();
}

View file

@ -1412,7 +1412,7 @@ public:
return _text.isEmpty() && !_media;
}
void clipCallback(ClipReaderNotification notification);
void clipCallback(Media::Clip::Notification notification);
virtual ~HistoryItem();
@ -1656,7 +1656,7 @@ public:
virtual DocumentData *getDocument() {
return nullptr;
}
virtual ClipReader *getClipReader() {
virtual Media::Clip::Reader *getClipReader() {
return nullptr;
}
@ -2140,7 +2140,7 @@ public:
DocumentData *getDocument() override {
return _data;
}
ClipReader *getClipReader() override {
Media::Clip::Reader *getClipReader() override {
return gif();
}
@ -2189,12 +2189,12 @@ private:
int32 _thumbw, _thumbh;
Text _caption;
ClipReader *_gif;
ClipReader *gif() {
return (_gif == BadClipReader) ? nullptr : _gif;
Media::Clip::Reader *_gif;
Media::Clip::Reader *gif() {
return (_gif == Media::Clip::BadReader) ? nullptr : _gif;
}
const ClipReader *gif() const {
return (_gif == BadClipReader) ? nullptr : _gif;
const Media::Clip::Reader *gif() const {
return (_gif == Media::Clip::BadReader) ? nullptr : _gif;
}
void setStatusSize(int32 newSize) const;
@ -2377,7 +2377,7 @@ public:
DocumentData *getDocument() override {
return _attach ? _attach->getDocument() : 0;
}
ClipReader *getClipReader() override {
Media::Clip::Reader *getClipReader() override {
return _attach ? _attach->getClipReader() : 0;
}
bool playInline(bool autoplay) override {

View file

@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "styles/style_overview.h"
#include "inline_bots/inline_bot_result.h"
#include "media/media_clip_reader.h"
#include "localstorage.h"
#include "mainwidget.h"
#include "lang.h"
@ -131,9 +132,9 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
document->automaticLoad(nullptr);
bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
if (loaded && !gif() && _gif != BadClipReader) {
if (loaded && !gif() && _gif != Media::Clip::BadReader) {
Gif *that = const_cast<Gif*>(this);
that->_gif = new ClipReader(document->location(), document->data(), func(that, &Gif::clipCallback));
that->_gif = new Media::Clip::Reader(document->location(), document->data(), func(that, &Gif::clipCallback));
if (gif()) _gif->setAutoplay();
}
@ -162,7 +163,7 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
}
}
if (radial || (!_gif && !loaded && !loading) || (_gif == BadClipReader)) {
if (radial || (!_gif && !loaded && !loading) || (_gif == Media::Clip::BadReader)) {
float64 radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1;
if (_animation && _animation->_a_over.animating(context->ms)) {
float64 over = _animation->_a_over.current();
@ -326,13 +327,14 @@ void Gif::step_radial(uint64 ms, bool timer) {
}
}
void Gif::clipCallback(ClipReaderNotification notification) {
void Gif::clipCallback(Media::Clip::Notification notification) {
using namespace Media::Clip;
switch (notification) {
case ClipReaderReinit: {
case NotificationReinit: {
if (gif()) {
if (_gif->state() == ClipError) {
if (_gif->state() == State::Error) {
delete _gif;
_gif = BadClipReader;
_gif = BadReader;
getShownDocument()->forget();
} else if (_gif->ready() && !_gif->started()) {
int32 height = st::inlineMediaHeight;
@ -348,7 +350,7 @@ void Gif::clipCallback(ClipReaderNotification notification) {
update();
} break;
case ClipReaderRepaint: {
case NotificationRepaint: {
if (gif() && !_gif->currentDisplayed()) {
update();
}

View file

@ -92,10 +92,10 @@ private:
return ~StateFlags(flag);
}
ClipReader *_gif = nullptr;
Media::Clip::Reader *_gif = nullptr;
ClickHandlerPtr _delete;
bool gif() const {
return (!_gif || _gif == BadClipReader) ? false : true;
return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
}
mutable QPixmap _thumb;
void prepareThumb(int32 width, int32 height, const QSize &frame) const;
@ -104,7 +104,7 @@ private:
bool isRadialAnimation(uint64 ms) const;
void step_radial(uint64 ms, bool timer);
void clipCallback(ClipReaderNotification notification);
void clipCallback(Media::Clip::Notification notification);
struct AnimationData {
AnimationData(AnimationCallbacks &&callbacks)

View file

@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "stdafx.h"
#include "lang.h"
#include "media/media_clip_reader.h"
#include "layerwidget.h"
#include "application.h"
#include "mainwindow.h"
@ -346,9 +347,9 @@ QPixmap MediaPreviewWidget::currentImage() const {
} else {
_document->automaticLoad(nullptr);
if (_document->loaded()) {
if (!_gif && _gif != BadClipReader) {
MediaPreviewWidget *that = const_cast<MediaPreviewWidget*>(this);
that->_gif = new ClipReader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback));
if (!_gif && _gif != Media::Clip::BadReader) {
auto that = const_cast<MediaPreviewWidget*>(this);
that->_gif = new Media::Clip::Reader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback));
if (gif()) _gif->setAutoplay();
}
}
@ -385,12 +386,13 @@ QPixmap MediaPreviewWidget::currentImage() const {
return _cache;
}
void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) {
void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) {
using namespace Media::Clip;
switch (notification) {
case ClipReaderReinit: {
if (gif() && _gif->state() == ClipError) {
case NotificationReinit: {
if (gif() && _gif->state() == State::Error) {
delete _gif;
_gif = BadClipReader;
_gif = BadReader;
}
if (gif() && _gif->ready() && !_gif->started()) {
@ -401,7 +403,7 @@ void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) {
update();
} break;
case ClipReaderRepaint: {
case NotificationRepaint: {
if (gif() && !_gif->currentDisplayed()) {
update();
}

View file

@ -135,12 +135,12 @@ private:
Animation _a_shown;
DocumentData *_document = nullptr;
PhotoData *_photo = nullptr;
ClipReader *_gif = nullptr;
Media::Clip::Reader *_gif = nullptr;
bool gif() const {
return (!_gif || _gif == BadClipReader) ? false : true;
return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
}
void clipCallback(ClipReaderNotification notification);
void clipCallback(Media::Clip::Notification notification);
enum CacheStatus {
CacheNotLoaded,

View file

@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "audio.h"
#include "boxes/photosendbox.h"
#include "media/media_clip_reader.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "lang.h"
@ -330,7 +331,7 @@ void FileLoadTask::process() {
}
if (filemime == qstr("video/mp4") || filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive) || animated) {
QImage cover;
MTPDocumentAttribute animatedAttribute = clipReadAnimatedAttributes(_filepath, _content, cover);
MTPDocumentAttribute animatedAttribute = Media::Clip::readAttributes(_filepath, _content, cover);
if (animatedAttribute.type() == mtpc_documentAttributeVideo) {
int32 cw = cover.width(), ch = cover.height();
if (cw < 20 * ch && ch < 20 * cw) {

View file

@ -0,0 +1,302 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "media/media_clip_ffmpeg.h"
namespace Media {
namespace Clip {
namespace internal {
FFMpegReaderImplementation::FFMpegReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) {
_frame = av_frame_alloc();
av_init_packet(&_avpkt);
_avpkt.data = NULL;
_avpkt.size = 0;
}
bool FFMpegReaderImplementation::readNextFrame() {
if (_frameRead) {
av_frame_unref(_frame);
_frameRead = false;
}
int res;
while (true) {
if (_avpkt.size > 0) { // previous packet not finished
res = 0;
} else if ((res = av_read_frame(_fmtContext, &_avpkt)) < 0) {
if (res != AVERROR_EOF || !_hadFrame) {
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
LOG(("Gif Error: Unable to av_read_frame() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
return false;
}
}
bool finished = (res < 0);
if (finished) {
_avpkt.data = NULL;
_avpkt.size = 0;
} else {
rememberPacket();
}
int32 got_frame = 0;
int32 decoded = _avpkt.size;
if (_avpkt.stream_index == _streamId) {
if ((res = avcodec_decode_video2(_codecContext, _frame, &got_frame, &_avpkt)) < 0) {
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
LOG(("Gif Error: Unable to avcodec_decode_video2() %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
if (res == AVERROR_INVALIDDATA) { // try to skip bad packet
freePacket();
_avpkt.data = NULL;
_avpkt.size = 0;
continue;
}
if (res != AVERROR_EOF || !_hadFrame) { // try to skip end of file
return false;
}
freePacket();
_avpkt.data = NULL;
_avpkt.size = 0;
continue;
}
if (res > 0) decoded = res;
} else if (_audioStreamId >= 0 && _avpkt.stream_index == _audioStreamId) {
freePacket();
continue;
}
if (!finished) {
_avpkt.data += decoded;
_avpkt.size -= decoded;
if (_avpkt.size <= 0) freePacket();
}
if (got_frame) {
int64 duration = av_frame_get_pkt_duration(_frame);
int64 framePts = (_frame->pkt_pts == AV_NOPTS_VALUE) ? _frame->pkt_dts : _frame->pkt_pts;
int64 frameMs = (framePts * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
_currentFrameDelay = _nextFrameDelay;
if (_frameMs + _currentFrameDelay < frameMs) {
_currentFrameDelay = int32(frameMs - _frameMs);
}
if (duration == AV_NOPTS_VALUE) {
_nextFrameDelay = 0;
} else {
_nextFrameDelay = (duration * 1000LL * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
}
_frameMs = frameMs;
_hadFrame = _frameRead = true;
return true;
}
if (finished) {
if ((res = avformat_seek_file(_fmtContext, _streamId, std::numeric_limits<int64_t>::min(), 0, std::numeric_limits<int64_t>::max(), 0)) < 0) {
if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_BYTE)) < 0) {
if ((res = av_seek_frame(_fmtContext, _streamId, 0, AVSEEK_FLAG_FRAME)) < 0) {
if ((res = av_seek_frame(_fmtContext, _streamId, 0, 0)) < 0) {
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
LOG(("Gif Error: Unable to av_seek_frame() to the start %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
return false;
}
}
}
}
avcodec_flush_buffers(_codecContext);
_hadFrame = false;
_frameMs = 0;
}
}
return false;
}
bool FFMpegReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
t_assert(_frameRead);
_frameRead = false;
if (!_width || !_height) {
_width = _frame->width;
_height = _frame->height;
if (!_width || !_height) {
LOG(("Gif Error: Bad frame size %1").arg(logData()));
return false;
}
}
QSize toSize(size.isEmpty() ? QSize(_width, _height) : size);
if (to.isNull() || to.size() != toSize) {
to = QImage(toSize, QImage::Format_ARGB32);
}
hasAlpha = (_frame->format == AV_PIX_FMT_BGRA || (_frame->format == -1 && _codecContext->pix_fmt == AV_PIX_FMT_BGRA));
if (_frame->width == toSize.width() && _frame->height == toSize.height() && hasAlpha) {
int32 sbpl = _frame->linesize[0], dbpl = to.bytesPerLine(), bpl = qMin(sbpl, dbpl);
uchar *s = _frame->data[0], *d = to.bits();
for (int32 i = 0, l = _frame->height; i < l; ++i) {
memcpy(d + i * dbpl, s + i * sbpl, bpl);
}
} else {
if ((_swsSize != toSize) || (_frame->format != -1 && _frame->format != _codecContext->pix_fmt) || !_swsContext) {
_swsSize = toSize;
_swsContext = sws_getCachedContext(_swsContext, _frame->width, _frame->height, AVPixelFormat(_frame->format), toSize.width(), toSize.height(), AV_PIX_FMT_BGRA, 0, 0, 0, 0);
}
uint8_t * toData[1] = { to.bits() };
int toLinesize[1] = { to.bytesPerLine() }, res;
if ((res = sws_scale(_swsContext, _frame->data, _frame->linesize, 0, _frame->height, toData, toLinesize)) != _swsSize.height()) {
LOG(("Gif Error: Unable to sws_scale to good size %1, height %2, should be %3").arg(logData()).arg(res).arg(_swsSize.height()));
return false;
}
}
av_frame_unref(_frame);
return true;
}
int FFMpegReaderImplementation::nextFrameDelay() {
return _currentFrameDelay;
}
bool FFMpegReaderImplementation::start(bool onlyGifv) {
initDevice();
if (!_device->open(QIODevice::ReadOnly)) {
LOG(("Gif Error: Unable to open device %1").arg(logData()));
return false;
}
_ioBuffer = (uchar*)av_malloc(AVBlockSize);
_ioContext = avio_alloc_context(_ioBuffer, AVBlockSize, 0, static_cast<void*>(this), &FFMpegReaderImplementation::_read, 0, &FFMpegReaderImplementation::_seek);
_fmtContext = avformat_alloc_context();
if (!_fmtContext) {
LOG(("Gif Error: Unable to avformat_alloc_context %1").arg(logData()));
return false;
}
_fmtContext->pb = _ioContext;
int res = 0;
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
if ((res = avformat_open_input(&_fmtContext, 0, 0, 0)) < 0) {
_ioBuffer = 0;
LOG(("Gif Error: Unable to avformat_open_input %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
return false;
}
_opened = true;
if ((res = avformat_find_stream_info(_fmtContext, 0)) < 0) {
LOG(("Gif Error: Unable to avformat_find_stream_info %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
return false;
}
_streamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, 0, 0);
if (_streamId < 0) {
LOG(("Gif Error: Unable to av_find_best_stream %1, error %2, %3").arg(logData()).arg(_streamId).arg(av_make_error_string(err, sizeof(err), _streamId)));
return false;
}
// Get a pointer to the codec context for the audio stream
_codecContext = _fmtContext->streams[_streamId]->codec;
_codec = avcodec_find_decoder(_codecContext->codec_id);
_audioStreamId = av_find_best_stream(_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, 0, 0);
if (onlyGifv) {
if (_audioStreamId >= 0) { // should be no audio stream
return false;
}
if (dataSize() > AnimationInMemory) {
return false;
}
if (_codecContext->codec_id != AV_CODEC_ID_H264) {
return false;
}
}
av_opt_set_int(_codecContext, "refcounted_frames", 1, 0);
if ((res = avcodec_open2(_codecContext, _codec, 0)) < 0) {
LOG(("Gif Error: Unable to avcodec_open2 %1, error %2, %3").arg(logData()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
return false;
}
return true;
}
QString FFMpegReaderImplementation::logData() const {
return qsl("for file '%1', data size '%2'").arg(_location ? _location->name() : QString()).arg(_data->size());
}
int FFMpegReaderImplementation::duration() const {
if (_fmtContext->streams[_streamId]->duration == AV_NOPTS_VALUE) return 0;
return (_fmtContext->streams[_streamId]->duration * _fmtContext->streams[_streamId]->time_base.num) / _fmtContext->streams[_streamId]->time_base.den;
}
FFMpegReaderImplementation::~FFMpegReaderImplementation() {
if (_frameRead) {
av_frame_unref(_frame);
_frameRead = false;
}
if (_ioContext) av_free(_ioContext);
if (_codecContext) avcodec_close(_codecContext);
if (_swsContext) sws_freeContext(_swsContext);
if (_opened) {
avformat_close_input(&_fmtContext);
} else if (_ioBuffer) {
av_free(_ioBuffer);
}
if (_fmtContext) avformat_free_context(_fmtContext);
av_frame_free(&_frame);
freePacket();
}
void FFMpegReaderImplementation::rememberPacket() {
if (!_packetWas) {
_packetSize = _avpkt.size;
_packetData = _avpkt.data;
_packetWas = true;
}
}
void FFMpegReaderImplementation::freePacket() {
if (_packetWas) {
_avpkt.size = _packetSize;
_avpkt.data = _packetData;
_packetWas = false;
av_packet_unref(&_avpkt);
}
}
int FFMpegReaderImplementation::_read(void *opaque, uint8_t *buf, int buf_size) {
FFMpegReaderImplementation *l = reinterpret_cast<FFMpegReaderImplementation*>(opaque);
return int(l->_device->read((char*)(buf), buf_size));
}
int64_t FFMpegReaderImplementation::_seek(void *opaque, int64_t offset, int whence) {
FFMpegReaderImplementation *l = reinterpret_cast<FFMpegReaderImplementation*>(opaque);
switch (whence) {
case SEEK_SET: return l->_device->seek(offset) ? l->_device->pos() : -1;
case SEEK_CUR: return l->_device->seek(l->_device->pos() + offset) ? l->_device->pos() : -1;
case SEEK_END: return l->_device->seek(l->_device->size() + offset) ? l->_device->pos() : -1;
}
return -1;
}
} // namespace internal
} // namespace Clip
} // namespace Media

View file

@ -0,0 +1,89 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}
#include "media/media_clip_implementation.h"
namespace Media {
namespace Clip {
namespace internal {
class FFMpegReaderImplementation : public ReaderImplementation {
public:
FFMpegReaderImplementation(FileLocation *location, QByteArray *data);
bool readNextFrame() override;
bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override;
int nextFrameDelay() override;
bool start(bool onlyGifv) override;
int duration() const;
QString logData() const;
~FFMpegReaderImplementation();
private:
void rememberPacket();
void freePacket();
static int _read(void *opaque, uint8_t *buf, int buf_size);
static int64_t _seek(void *opaque, int64_t offset, int whence);
uchar *_ioBuffer = nullptr;
AVIOContext *_ioContext = nullptr;
AVFormatContext *_fmtContext = nullptr;
AVCodec *_codec = nullptr;
AVCodecContext *_codecContext = nullptr;
int _streamId = 0;
AVFrame *_frame = nullptr;
bool _opened = false;
bool _hadFrame = false;
bool _frameRead = false;
int _audioStreamId = 0;
AVPacket _avpkt;
int _packetSize = 0;
uint8_t *_packetData = nullptr;
bool _packetWas = false;
int _width = 0;
int _height = 0;
SwsContext *_swsContext = nullptr;
QSize _swsSize;
int64 _frameMs = 0;
int _nextFrameDelay = 0;
int _currentFrameDelay = 0;
};
} // namespace internal
} // namespace Clip
} // namespace Media

View file

@ -0,0 +1,43 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "media/media_clip_implementation.h"
namespace Media {
namespace Clip {
namespace internal {
void ReaderImplementation::initDevice() {
if (_data->isEmpty()) {
if (_file.isOpen()) _file.close();
_file.setFileName(_location->name());
_dataSize = _file.size();
} else {
if (_buffer.isOpen()) _buffer.close();
_buffer.setBuffer(_data);
_dataSize = _data->size();
}
_device = _data->isEmpty() ? static_cast<QIODevice*>(&_file) : static_cast<QIODevice*>(&_buffer);
}
} // namespace internal
} // namespace Clip
} // namespace Media

View file

@ -0,0 +1,60 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
class FileLocation;
namespace Media {
namespace Clip {
namespace internal {
class ReaderImplementation {
public:
ReaderImplementation(FileLocation *location, QByteArray *data)
: _location(location)
, _data(data) {
}
virtual bool readNextFrame() = 0;
virtual bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) = 0;
virtual int nextFrameDelay() = 0;
virtual bool start(bool onlyGifv) = 0;
virtual ~ReaderImplementation() {
}
int64 dataSize() const {
return _dataSize;
}
protected:
FileLocation *_location;
QByteArray *_data;
QFile _file;
QBuffer _buffer;
QIODevice *_device = nullptr;
int64 _dataSize = 0;
void initDevice();
};
} // namespace internal
} // namespace Clip
} // namespace Media

View file

@ -0,0 +1,106 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "media/media_clip_qtgif.h"
namespace Media {
namespace Clip {
namespace internal {
QtGifReaderImplementation::QtGifReaderImplementation(FileLocation *location, QByteArray *data) : ReaderImplementation(location, data) {
}
bool QtGifReaderImplementation::readNextFrame() {
if (_reader) _frameDelay = _reader->nextImageDelay();
if (_framesLeft < 1 && !jumpToStart()) {
return false;
}
_frame = QImage(); // QGifHandler always reads first to internal QImage and returns it
if (!_reader->read(&_frame) || _frame.isNull()) {
return false;
}
--_framesLeft;
return true;
}
bool QtGifReaderImplementation::renderFrame(QImage &to, bool &hasAlpha, const QSize &size) {
t_assert(!_frame.isNull());
if (size.isEmpty() || size == _frame.size()) {
int32 w = _frame.width(), h = _frame.height();
if (to.width() == w && to.height() == h && to.format() == _frame.format()) {
if (to.byteCount() != _frame.byteCount()) {
int bpl = qMin(to.bytesPerLine(), _frame.bytesPerLine());
for (int i = 0; i < h; ++i) {
memcpy(to.scanLine(i), _frame.constScanLine(i), bpl);
}
} else {
memcpy(to.bits(), _frame.constBits(), _frame.byteCount());
}
} else {
to = _frame.copy();
}
} else {
to = _frame.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
hasAlpha = _frame.hasAlphaChannel();
_frame = QImage();
return true;
}
int QtGifReaderImplementation::nextFrameDelay() {
return _frameDelay;
}
bool QtGifReaderImplementation::start(bool onlyGifv) {
if (onlyGifv) return false;
return jumpToStart();
}
QtGifReaderImplementation::~QtGifReaderImplementation() {
deleteAndMark(_reader);
}
bool QtGifReaderImplementation::jumpToStart() {
if (_reader && _reader->jumpToImage(0)) {
_framesLeft = _reader->imageCount();
return true;
}
delete _reader;
initDevice();
_reader = new QImageReader(_device);
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
_reader->setAutoTransform(true);
#endif
if (!_reader->canRead() || !_reader->supportsAnimation()) {
return false;
}
_framesLeft = _reader->imageCount();
if (_framesLeft < 1) {
return false;
}
return true;
}
} // namespace internal
} // namespace Clip
} // namespace Media

View file

@ -0,0 +1,53 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "media/media_clip_implementation.h"
namespace Media {
namespace Clip {
namespace internal {
class QtGifReaderImplementation : public ReaderImplementation {
public:
QtGifReaderImplementation(FileLocation *location, QByteArray *data);
bool readNextFrame() override;
bool renderFrame(QImage &to, bool &hasAlpha, const QSize &size) override;
int nextFrameDelay() override;
bool start(bool onlyGifv) override;
~QtGifReaderImplementation();
private:
bool jumpToStart();
QImageReader *_reader = nullptr;
int _framesLeft = 0;
int _frameDelay = 0;
QImage _frame;
};
} // namespace internal
} // namespace Clip
} // namespace Media

View file

@ -0,0 +1,731 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "media/media_clip_reader.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}
#include "media/media_clip_ffmpeg.h"
#include "media/media_clip_qtgif.h"
#include "mainwidget.h"
#include "mainwindow.h"
namespace Media {
namespace Clip {
namespace {
QVector<QThread*> threads;
QVector<Manager*> managers;
QPixmap _prepareFrame(const FrameRequest &request, const QImage &original, bool hasAlpha, QImage &cache) {
bool badSize = (original.width() != request.framew) || (original.height() != request.frameh);
bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh);
if (badSize || needOuter || hasAlpha || request.rounded) {
int32 factor(request.factor);
bool newcache = (cache.width() != request.outerw || cache.height() != request.outerh);
if (newcache) {
cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied);
cache.setDevicePixelRatio(factor);
}
{
Painter p(&cache);
if (newcache) {
if (request.framew < request.outerw) {
p.fillRect(0, 0, (request.outerw - request.framew) / (2 * factor), cache.height() / factor, st::black);
p.fillRect((request.outerw - request.framew) / (2 * factor) + (request.framew / factor), 0, (cache.width() / factor) - ((request.outerw - request.framew) / (2 * factor) + (request.framew / factor)), cache.height() / factor, st::black);
}
if (request.frameh < request.outerh) {
p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), 0, qMin(cache.width(), request.framew) / factor, (request.outerh - request.frameh) / (2 * factor), st::black);
p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), (request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor), qMin(cache.width(), request.framew) / factor, (cache.height() / factor) - ((request.outerh - request.frameh) / (2 * factor) + (request.frameh / factor)), st::black);
}
}
if (hasAlpha) {
p.fillRect(qMax(0, (request.outerw - request.framew) / (2 * factor)), qMax(0, (request.outerh - request.frameh) / (2 * factor)), qMin(cache.width(), request.framew) / factor, qMin(cache.height(), request.frameh) / factor, st::white);
}
QPoint position((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor));
if (badSize) {
p.setRenderHint(QPainter::SmoothPixmapTransform);
QRect to(position, QSize(request.framew / factor, request.frameh / factor));
QRect from(0, 0, original.width(), original.height());
p.drawImage(to, original, from, Qt::ColorOnly);
} else {
p.drawImage(position, original);
}
}
if (request.rounded) {
imageRound(cache);
}
return QPixmap::fromImage(cache, Qt::ColorOnly);
}
return QPixmap::fromImage(original, Qt::ColorOnly);
}
} // namespace
Reader::Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode)
: _callback(std_::move(callback))
, _mode(mode) {
if (threads.size() < ClipThreadsCount) {
_threadIndex = threads.size();
threads.push_back(new QThread());
managers.push_back(new Manager(threads.back()));
threads.back()->start();
} else {
_threadIndex = int32(rand_value<uint32>() % threads.size());
int32 loadLevel = 0x7FFFFFFF;
for (int32 i = 0, l = threads.size(); i < l; ++i) {
int32 level = managers.at(i)->loadLevel();
if (level < loadLevel) {
_threadIndex = i;
loadLevel = level;
}
}
}
managers.at(_threadIndex)->append(this, location, data);
}
Reader::Frame *Reader::frameToShow(int32 *index) const { // 0 means not ready
int32 step = _step.loadAcquire(), i;
if (step == WaitingForDimensionsStep) {
if (index) *index = 0;
return 0;
} else if (step == WaitingForRequestStep) {
i = 0;
} else if (step == WaitingForFirstFrameStep) {
i = 0;
} else {
i = (step / 2) % 3;
}
if (index) *index = i;
return _frames + i;
}
Reader::Frame *Reader::frameToWrite(int32 *index) const { // 0 means not ready
int32 step = _step.loadAcquire(), i;
if (step == WaitingForDimensionsStep) {
i = 0;
} else if (step == WaitingForRequestStep) {
if (index) *index = 0;
return 0;
} else if (step == WaitingForFirstFrameStep) {
i = 0;
} else {
i = ((step + 2) / 2) % 3;
}
if (index) *index = i;
return _frames + i;
}
Reader::Frame *Reader::frameToWriteNext(bool checkNotWriting, int32 *index) const {
int32 step = _step.loadAcquire(), i;
if (step == WaitingForDimensionsStep || step == WaitingForRequestStep || (checkNotWriting && (step % 2))) {
if (index) *index = 0;
return 0;
}
i = ((step + 4) / 2) % 3;
if (index) *index = i;
return _frames + i;
}
void Reader::moveToNextShow() const {
int32 step = _step.loadAcquire();
if (step == WaitingForDimensionsStep) {
} else if (step == WaitingForRequestStep) {
_step.storeRelease(WaitingForFirstFrameStep);
} else if (step == WaitingForFirstFrameStep) {
} else if (!(step % 2)) {
_step.storeRelease(step + 1);
}
}
void Reader::moveToNextWrite() const {
int32 step = _step.loadAcquire();
if (step == WaitingForDimensionsStep) {
_step.storeRelease(WaitingForRequestStep);
} else if (step == WaitingForRequestStep) {
} else if (step == WaitingForFirstFrameStep) {
_step.storeRelease(0);
} else if (step % 2) {
_step.storeRelease((step + 1) % 6);
}
}
void Reader::callback(Reader *reader, int32 threadIndex, Notification notification) {
// check if reader is not deleted already
if (managers.size() > threadIndex && managers.at(threadIndex)->carries(reader)) {
reader->_callback.call(notification);
}
}
void Reader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) {
if (managers.size() <= _threadIndex) error();
if (_state == State::Error) return;
if (_step.loadAcquire() == WaitingForRequestStep) {
int factor = cIntRetinaFactor();
FrameRequest request;
request.factor = factor;
request.framew = framew * factor;
request.frameh = frameh * factor;
request.outerw = outerw * factor;
request.outerh = outerh * factor;
request.rounded = rounded;
_frames[0].request = _frames[1].request = _frames[2].request = request;
moveToNextShow();
managers.at(_threadIndex)->start(this);
}
}
QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms) {
Frame *frame = frameToShow();
t_assert(frame != 0);
if (ms) {
frame->displayed.storeRelease(1);
if (_paused.loadAcquire()) {
_paused.storeRelease(0);
if (managers.size() <= _threadIndex) error();
if (_state != State::Error) {
managers.at(_threadIndex)->update(this);
}
}
} else {
frame->displayed.storeRelease(-1); // displayed, but should be paused
}
int32 factor(cIntRetinaFactor());
if (frame->pix.width() == outerw * factor && frame->pix.height() == outerh * factor) {
moveToNextShow();
return frame->pix;
}
frame->request.framew = framew * factor;
frame->request.frameh = frameh * factor;
frame->request.outerw = outerw * factor;
frame->request.outerh = outerh * factor;
QImage cacheForResize;
frame->original.setDevicePixelRatio(factor);
frame->pix = QPixmap();
frame->pix = _prepareFrame(frame->request, frame->original, true, cacheForResize);
Frame *other = frameToWriteNext(true);
if (other) other->request = frame->request;
moveToNextShow();
if (managers.size() <= _threadIndex) error();
if (_state != State::Error) {
managers.at(_threadIndex)->update(this);
}
return frame->pix;
}
bool Reader::ready() const {
if (_width && _height) return true;
Frame *frame = frameToShow();
if (frame) {
_width = frame->original.width();
_height = frame->original.height();
return true;
}
return false;
}
int32 Reader::width() const {
return _width;
}
int32 Reader::height() const {
return _height;
}
State Reader::state() const {
return _state;
}
void Reader::stop() {
if (managers.size() <= _threadIndex) error();
if (_state != State::Error) {
managers.at(_threadIndex)->stop(this);
_width = _height = 0;
}
}
void Reader::error() {
_state = State::Error;
}
Reader::~Reader() {
stop();
}
class ReaderPrivate {
public:
ReaderPrivate(Reader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader)
, _data(data)
, _location(_data.isEmpty() ? new FileLocation(location) : 0) {
if (_data.isEmpty() && !_location->accessEnable()) {
error();
return;
}
_accessed = true;
}
ProcessResult start(uint64 ms) {
if (!_implementation && !init()) {
return error();
}
if (frame() && frame()->original.isNull()) {
if (!_implementation->readNextFrame()) {
return error();
}
if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize())) {
return error();
}
_width = frame()->original.width();
_height = frame()->original.height();
return ProcessResult::Started;
}
return ProcessResult::Wait;
}
ProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit
if (_state == State::Error) return ProcessResult::Error;
if (!_request.valid()) {
return start(ms);
}
if (!_paused && ms >= _nextFrameWhen) {
return ProcessResult::Repaint;
}
return ProcessResult::Wait;
}
ProcessResult finishProcess(uint64 ms) {
if (!readNextFrame()) {
return error();
}
if (ms >= _nextFrameWhen && !readNextFrame(true)) {
return error();
}
if (!renderFrame()) {
return error();
}
return ProcessResult::CopyFrame;
}
uint64 nextFrameDelay() {
int32 delay = _implementation->nextFrameDelay();
return qMax(delay, 5);
}
bool readNextFrame(bool keepup = false) {
if (!_implementation->readNextFrame()) {
return false;
}
_nextFrameWhen += nextFrameDelay();
if (keepup) {
_nextFrameWhen = qMax(_nextFrameWhen, getms());
}
return true;
}
bool renderFrame() {
t_assert(frame() != 0 && _request.valid());
if (!_implementation->renderFrame(frame()->original, frame()->alpha, QSize(_request.framew, _request.frameh))) {
return false;
}
frame()->original.setDevicePixelRatio(_request.factor);
frame()->pix = QPixmap();
frame()->pix = _prepareFrame(_request, frame()->original, frame()->alpha, frame()->cache);
frame()->when = _nextFrameWhen;
return true;
}
bool init() {
if (_data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) {
QFile f(_location->name());
if (f.open(QIODevice::ReadOnly)) {
_data = f.readAll();
if (f.error() != QFile::NoError) {
_data = QByteArray();
}
}
}
_implementation = new internal::FFMpegReaderImplementation(_location, &_data);
// _implementation = new QtGifReaderImplementation(_location, &_data);
return _implementation->start(false);
}
ProcessResult error() {
stop();
_state = State::Error;
return ProcessResult::Error;
}
void stop() {
delete _implementation;
_implementation = 0;
if (_location) {
if (_accessed) {
_location->accessDisable();
}
delete _location;
_location = 0;
}
_accessed = false;
}
~ReaderPrivate() {
stop();
deleteAndMark(_location);
deleteAndMark(_implementation);
_data.clear();
}
private:
Reader *_interface;
State _state = State::Reading;
QByteArray _data;
FileLocation *_location;
bool _accessed = false;
QBuffer _buffer;
internal::ReaderImplementation *_implementation = nullptr;
FrameRequest _request;
struct Frame {
QPixmap pix;
QImage original, cache;
bool alpha = true;
uint64 when = 0;
};
Frame _frames[3];
int _frame = 0;
Frame *frame() {
return _frames + _frame;
}
int _width = 0;
int _height = 0;
uint64 _nextFrameWhen = 0;
bool _paused = false;
friend class Manager;
};
Manager::Manager(QThread *thread) : _processingInThread(0), _needReProcess(false) {
moveToThread(thread);
connect(thread, SIGNAL(started()), this, SLOT(process()));
connect(thread, SIGNAL(finished()), this, SLOT(finish()));
connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection);
_timer.setSingleShot(true);
_timer.moveToThread(thread);
connect(&_timer, SIGNAL(timeout()), this, SLOT(process()));
anim::registerClipManager(this);
}
void Manager::append(Reader *reader, const FileLocation &location, const QByteArray &data) {
reader->_private = new ReaderPrivate(reader, location, data);
_loadLevel.fetchAndAddRelaxed(AverageGifSize);
update(reader);
}
void Manager::start(Reader *reader) {
update(reader);
}
void Manager::update(Reader *reader) {
QReadLocker lock(&_readerPointersMutex);
ReaderPointers::const_iterator i = _readerPointers.constFind(reader);
if (i == _readerPointers.cend()) {
lock.unlock();
QWriteLocker lock(&_readerPointersMutex);
_readerPointers.insert(reader, MutableAtomicInt(1));
} else {
i->v.storeRelease(1);
}
emit processDelayed();
}
void Manager::stop(Reader *reader) {
if (!carries(reader)) return;
QWriteLocker lock(&_readerPointersMutex);
_readerPointers.remove(reader);
emit processDelayed();
}
bool Manager::carries(Reader *reader) const {
QReadLocker lock(&_readerPointersMutex);
return _readerPointers.contains(reader);
}
Manager::ReaderPointers::iterator Manager::unsafeFindReaderPointer(ReaderPrivate *reader) {
ReaderPointers::iterator it = _readerPointers.find(reader->_interface);
// could be a new reader which was realloced in the same address
return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.end();
}
Manager::ReaderPointers::const_iterator Manager::constUnsafeFindReaderPointer(ReaderPrivate *reader) const {
ReaderPointers::const_iterator it = _readerPointers.constFind(reader->_interface);
// could be a new reader which was realloced in the same address
return (it == _readerPointers.cend() || it.key()->_private == reader) ? it : _readerPointers.cend();
}
bool Manager::handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) {
QReadLocker lock(&_readerPointersMutex);
ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader);
if (result == ProcessResult::Error) {
if (it != _readerPointers.cend()) {
it.key()->error();
emit callback(it.key(), it.key()->threadIndex(), NotificationReinit);
lock.unlock();
QWriteLocker lock(&_readerPointersMutex);
ReaderPointers::iterator i = unsafeFindReaderPointer(reader);
if (i != _readerPointers.cend()) _readerPointers.erase(i);
}
return false;
}
if (it == _readerPointers.cend()) {
return false;
}
if (result == ProcessResult::Started) {
_loadLevel.fetchAndAddRelaxed(reader->_width * reader->_height - AverageGifSize);
}
if (!reader->_paused && result == ProcessResult::Repaint) {
int32 ishowing, iprevious;
Reader::Frame *showing = it.key()->frameToShow(&ishowing), *previous = it.key()->frameToWriteNext(false, &iprevious);
t_assert(previous != 0 && showing != 0 && ishowing >= 0 && iprevious >= 0);
if (reader->_frames[ishowing].when > 0 && showing->displayed.loadAcquire() <= 0) { // current frame was not shown
if (reader->_frames[ishowing].when + WaitBeforeGifPause < ms || (reader->_frames[iprevious].when && previous->displayed.loadAcquire() <= 0)) {
reader->_paused = true;
it.key()->_paused.storeRelease(1);
result = ProcessResult::Paused;
}
}
}
if (result == ProcessResult::Started || result == ProcessResult::CopyFrame) {
t_assert(reader->_frame >= 0);
Reader::Frame *frame = it.key()->_frames + reader->_frame;
frame->clear();
frame->pix = reader->frame()->pix;
frame->original = reader->frame()->original;
frame->displayed.storeRelease(0);
if (result == ProcessResult::Started) {
reader->_nextFrameWhen = ms;
it.key()->moveToNextWrite();
emit callback(it.key(), it.key()->threadIndex(), NotificationReinit);
}
} else if (result == ProcessResult::Paused) {
it.key()->moveToNextWrite();
emit callback(it.key(), it.key()->threadIndex(), NotificationReinit);
} else if (result == ProcessResult::Repaint) {
it.key()->moveToNextWrite();
emit callback(it.key(), it.key()->threadIndex(), NotificationRepaint);
}
return true;
}
Manager::ResultHandleState Manager::handleResult(ReaderPrivate *reader, ProcessResult result, uint64 ms) {
if (!handleProcessResult(reader, result, ms)) {
_loadLevel.fetchAndAddRelaxed(-1 * (reader->_width > 0 ? reader->_width * reader->_height : AverageGifSize));
delete reader;
return ResultHandleRemove;
}
_processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents);
if (_processingInThread->isInterruptionRequested()) {
return ResultHandleStop;
}
if (result == ProcessResult::Repaint) {
{
QReadLocker lock(&_readerPointersMutex);
ReaderPointers::const_iterator it = constUnsafeFindReaderPointer(reader);
if (it != _readerPointers.cend()) {
int32 index = 0;
Reader *r = it.key();
Reader::Frame *frame = it.key()->frameToWrite(&index);
if (frame) {
frame->clear();
} else {
t_assert(!reader->_request.valid());
}
reader->_frame = index;
}
}
return handleResult(reader, reader->finishProcess(ms), ms);
}
return ResultHandleContinue;
}
void Manager::process() {
if (_processingInThread) {
_needReProcess = true;
return;
}
_timer.stop();
_processingInThread = thread();
uint64 ms = getms(), minms = ms + 86400 * 1000ULL;
{
QReadLocker lock(&_readerPointersMutex);
for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) {
if (it->v.loadAcquire()) {
Readers::iterator i = _readers.find(it.key()->_private);
if (i == _readers.cend()) {
_readers.insert(it.key()->_private, 0);
} else {
i.value() = ms;
if (i.key()->_paused && !it.key()->_paused.loadAcquire()) {
i.key()->_paused = false;
}
}
Reader::Frame *frame = it.key()->frameToWrite();
if (frame) it.key()->_private->_request = frame->request;
it->v.storeRelease(0);
}
}
}
for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) {
ReaderPrivate *reader = i.key();
if (i.value() <= ms) {
ResultHandleState state = handleResult(reader, reader->process(ms), ms);
if (state == ResultHandleRemove) {
i = _readers.erase(i);
continue;
} else if (state == ResultHandleStop) {
_processingInThread = 0;
return;
}
ms = getms();
i.value() = reader->_nextFrameWhen ? reader->_nextFrameWhen : (ms + 86400 * 1000ULL);
}
if (!reader->_paused && i.value() < minms) {
minms = i.value();
}
++i;
}
ms = getms();
if (_needReProcess || minms <= ms) {
_needReProcess = false;
_timer.start(1);
} else {
_timer.start(minms - ms);
}
_processingInThread = 0;
}
void Manager::finish() {
_timer.stop();
clear();
}
void Manager::clear() {
{
QWriteLocker lock(&_readerPointersMutex);
for (ReaderPointers::iterator it = _readerPointers.begin(), e = _readerPointers.end(); it != e; ++it) {
it.key()->_private = 0;
}
_readerPointers.clear();
}
for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e; ++i) {
delete i.key();
}
_readers.clear();
}
Manager::~Manager() {
clear();
}
MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data, QImage &cover) {
FileLocation localloc(StorageFilePartial, fname);
QByteArray localdata(data);
auto reader = std_::make_unique<internal::FFMpegReaderImplementation>(&localloc, &localdata);
if (reader->start(true)) {
bool hasAlpha = false;
if (reader->readNextFrame() && reader->renderFrame(cover, hasAlpha, QSize())) {
if (cover.width() > 0 && cover.height() > 0 && cover.width() < cover.height() * 10 && cover.height() < cover.width() * 10) {
if (hasAlpha) {
QImage cacheForResize;
FrameRequest request;
request.framew = request.outerw = cover.width();
request.frameh = request.outerh = cover.height();
request.factor = 1;
cover = _prepareFrame(request, cover, hasAlpha, cacheForResize).toImage();
}
int duration = reader->duration();
return MTP_documentAttributeVideo(MTP_int(duration), MTP_int(cover.width()), MTP_int(cover.height()));
}
}
}
return MTP_documentAttributeFilename(MTP_string(fname));
}
void Finish() {
if (!threads.isEmpty()) {
for (int32 i = 0, l = threads.size(); i < l; ++i) {
threads.at(i)->quit();
DEBUG_LOG(("Waiting for clipThread to finish: %1").arg(i));
threads.at(i)->wait();
delete managers.at(i);
delete threads.at(i);
}
threads.clear();
managers.clear();
}
}
} // namespace Clip
} // namespace Media

View file

@ -0,0 +1,222 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
class FileLocation;
namespace Media {
namespace Clip {
enum class State {
Reading,
Error,
};
struct FrameRequest {
bool valid() const {
return factor > 0;
}
int factor = 0;
int framew = 0;
int frameh = 0;
int outerw = 0;
int outerh = 0;
bool rounded = false;
};
enum ReaderSteps {
WaitingForDimensionsStep = -3, // before ReaderPrivate read the first image and got the original frame size
WaitingForRequestStep = -2, // before Reader got the original frame size and prepared the frame request
WaitingForFirstFrameStep = -1, // before ReaderPrivate got the frame request and started waiting for the 1-2 delay
};
class ReaderPrivate;
class Reader {
public:
using Callback = Function<void, Notification>;
enum class Mode {
Gif,
Video,
};
Reader(const FileLocation &location, const QByteArray &data, Callback &&callback, Mode mode = Mode::Gif);
static void callback(Reader *reader, int threadIndex, Notification notification); // reader can be deleted
void setAutoplay() {
_autoplay = true;
}
bool autoplay() const {
return _autoplay;
}
void start(int framew, int frameh, int outerw, int outerh, bool rounded);
QPixmap current(int framew, int frameh, int outerw, int outerh, uint64 ms);
QPixmap frameOriginal() const {
Frame *frame = frameToShow();
if (!frame) return QPixmap();
QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap());
result.detach();
return result;
}
bool currentDisplayed() const {
Frame *frame = frameToShow();
return frame ? (frame->displayed.loadAcquire() != 0) : true;
}
bool paused() const {
return _paused.loadAcquire();
}
int threadIndex() const {
return _threadIndex;
}
int width() const;
int height() const;
State state() const;
bool started() const {
int step = _step.loadAcquire();
return (step == WaitingForFirstFrameStep) || (step >= 0);
}
bool ready() const;
void stop();
void error();
~Reader();
private:
Callback _callback;
Mode _mode;
State _state = State::Reading;
mutable int _width = 0;
mutable int _height = 0;
// -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3
mutable QAtomicInt _step = WaitingForDimensionsStep;
struct Frame {
Frame() : displayed(false) {
}
void clear() {
pix = QPixmap();
original = QImage();
}
QPixmap pix;
QImage original;
FrameRequest request;
QAtomicInt displayed;
};
mutable Frame _frames[3];
Frame *frameToShow(int *index = 0) const; // 0 means not ready
Frame *frameToWrite(int *index = 0) const; // 0 means not ready
Frame *frameToWriteNext(bool check, int *index = 0) const;
void moveToNextShow() const;
void moveToNextWrite() const;
QAtomicInt _paused = 0;
int32 _threadIndex;
bool _autoplay = false;
friend class Manager;
ReaderPrivate *_private = nullptr;
};
enum class ProcessResult {
Error,
Started,
Paused,
Repaint,
CopyFrame,
Wait,
};
class Manager : public QObject {
Q_OBJECT
public:
Manager(QThread *thread);
int32 loadLevel() const {
return _loadLevel.load();
}
void append(Reader *reader, const FileLocation &location, const QByteArray &data);
void start(Reader *reader);
void update(Reader *reader);
void stop(Reader *reader);
bool carries(Reader *reader) const;
~Manager();
signals:
void processDelayed();
void callback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification);
public slots:
void process();
void finish();
private:
void clear();
QAtomicInt _loadLevel;
struct MutableAtomicInt {
MutableAtomicInt(int value) : v(value) {
}
mutable QAtomicInt v;
};
typedef QMap<Reader*, MutableAtomicInt> ReaderPointers;
ReaderPointers _readerPointers;
mutable QReadWriteLock _readerPointersMutex;
ReaderPointers::const_iterator constUnsafeFindReaderPointer(ReaderPrivate *reader) const;
ReaderPointers::iterator unsafeFindReaderPointer(ReaderPrivate *reader);
bool handleProcessResult(ReaderPrivate *reader, ProcessResult result, uint64 ms);
enum ResultHandleState {
ResultHandleRemove,
ResultHandleStop,
ResultHandleContinue,
};
ResultHandleState handleResult(ReaderPrivate *reader, ProcessResult result, uint64 ms);
typedef QMap<ReaderPrivate*, uint64> Readers;
Readers _readers;
QTimer _timer;
QThread *_processingInThread;
bool _needReProcess;
};
MTPDocumentAttribute readAttributes(const QString &fname, const QByteArray &data, QImage &cover);
void Finish();
} // namespace Clip
} // namespace Media

View file

@ -26,8 +26,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
#include "application.h"
#include "ui/filedialog.h"
#include "media/media_clip_reader.h"
namespace {
class SaveMsgClickHandler : public ClickHandler {
public:
@ -70,7 +72,8 @@ namespace {
}
return false;
}
}
} // namespace
MediaView::MediaView() : TWidget(App::wnd())
, _animStarted(getms())
@ -219,7 +222,7 @@ bool MediaView::gifShown() const {
_gif->start(_gif->width(), _gif->height(), _gif->width(), _gif->height(), false);
const_cast<MediaView*>(this)->_current = QPixmap();
}
return _gif->state() != ClipError;
return _gif->state() != Media::Clip::State::Error;
}
return false;
}
@ -479,13 +482,13 @@ void MediaView::step_radial(uint64 ms, bool timer) {
if (timer && _radial.animating()) {
update(radialRect());
}
if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation())) {
if (!_doc->data().isEmpty() && _doc->isAnimation()) {
if (_doc && _doc->loaded() && _doc->size < MediaViewImageSizeLimit && (!_radial.animating() || _doc->isAnimation() || _doc->isVideo())) {
if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) {
displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid));
} else {
const FileLocation &location(_doc->location(true));
if (location.accessEnable()) {
if (_doc->isAnimation() || QImageReader(location.name()).canRead()) {
if (_doc->isAnimation() || _doc->isVideo() || QImageReader(location.name()).canRead()) {
displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid));
}
location.accessDisable();
@ -654,13 +657,15 @@ void MediaView::onDocClick() {
}
}
void MediaView::clipCallback(ClipReaderNotification notification) {
void MediaView::clipCallback(Media::Clip::Notification notification) {
using namespace Media::Clip;
if (!_gif) return;
switch (notification) {
case ClipReaderReinit: {
case NotificationReinit: {
if (HistoryItem *item = App::histItemById(_msgmigrated ? 0 : _channel, _msgid)) {
if (_gif->state() == ClipError) {
if (_gif->state() == State::Error) {
_current = QPixmap();
}
displayDocument(_doc, item);
@ -669,7 +674,7 @@ void MediaView::clipCallback(ClipReaderNotification notification) {
}
} break;
case ClipReaderRepaint: {
case NotificationRepaint: {
if (!_gif->currentDisplayed()) {
update(_x, _y, _w, _h);
}
@ -1027,7 +1032,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
}
void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty messages shown as docs: doc can be NULL
if (!doc || !doc->isAnimation() || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) {
if (!doc || (!doc->isAnimation() && !doc->isVideo()) || doc != _doc || (item && (item->id != _msgid || (item->history() != (_msgmigrated ? _migrated : _history))))) {
stopGif();
}
_doc = doc;
@ -1049,20 +1054,20 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty
_doc->automaticLoad(item);
const FileLocation &location(_doc->location(true));
if (!_doc->data().isEmpty() && _doc->isAnimation()) {
if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) {
if (!_gif) {
if (_doc->dimensions.width() && _doc->dimensions.height()) {
_current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height());
}
_gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback));
_gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback));
}
} else if (location.accessEnable()) {
if (_doc->isAnimation()) {
if (_doc->isAnimation() || _doc->isVideo()) {
if (!_gif) {
if (_doc->dimensions.width() && _doc->dimensions.height()) {
_current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height());
}
_gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback));
_gif = new Media::Clip::Reader(location, _doc->data(), func(this, &MediaView::clipCallback));
}
} else {
if (QImageReader(location.name()).canRead()) {

View file

@ -71,7 +71,7 @@ public:
void activateControls();
void onDocClick();
void clipCallback(ClipReaderNotification notification);
void clipCallback(Media::Clip::Notification notification);
PeerData *ui_getPeerForMouseAction();
~MediaView();
@ -169,7 +169,7 @@ private:
bool _pressed = false;
int32 _dragging = 0;
QPixmap _current;
ClipReader *_gif = nullptr;
Media::Clip::Reader *_gif = nullptr;
int32 _full = -1; // -1 - thumb, 0 - medium, 1 - full
bool fileShown() const;

View file

@ -947,9 +947,10 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) {
}
bool playVoice = data->voice() && audioPlayer();
bool playMusic = data->song() && audioPlayer();
bool playVideo = data->isVideo() && audioPlayer();
bool playAnimation = data->isAnimation() && item && item->getMedia();
const FileLocation &location(data->location(true));
if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playAnimation))) {
if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) {
if (playVoice) {
AudioMsgId playing;
AudioPlayerState playingState = AudioPlayerStopped;
@ -975,6 +976,16 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) {
audioPlayer()->play(song);
if (App::main()) App::main()->documentPlayProgress(song);
}
} else if (playVideo) {
if (!data->data().isEmpty()) {
App::wnd()->showDocument(data, item);
} else if (location.accessEnable()) {
App::wnd()->showDocument(data, item);
location.accessDisable();
} else {
psOpenFile(location.name());
}
if (App::main()) App::main()->mediaMarkRead(data);
} else if (data->voice() || data->isVideo()) {
psOpenFile(location.name());
if (App::main()) App::main()->mediaMarkRead(data);

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include <QtCore/QTimer>
#include <QtGui/QColor>
namespace Media {
namespace Clip {
class Reader;
static Reader * const BadReader = SharedMemoryLocation<Reader, 0>();
class Manager;
enum Notification {
NotificationReinit,
NotificationRepaint,
};
} // namespace Clip
} // namespace Media
namespace anim {
typedef float64 (*transition)(const float64 &delta, const float64 &dt);
@ -193,6 +209,7 @@ namespace anim {
void startManager();
void stopManager();
void registerClipManager(Media::Clip::Manager *manager);
};
@ -480,8 +497,6 @@ if ((animation).isNull()) { \
ENSURE_ANIMATION(animation, updateCallback, from); \
(animation).start((to), (duration), (transition))
class ClipReader;
class AnimationManager : public QObject {
Q_OBJECT
@ -494,7 +509,7 @@ public:
public slots:
void timeout();
void clipCallback(ClipReader *reader, qint32 threadIndex, qint32 notification);
void clipCallback(Media::Clip::Reader *reader, qint32 threadIndex, qint32 notification);
private:
typedef QMap<Animation*, NullType> AnimatingObjects;
@ -503,198 +518,3 @@ private:
bool _iterating;
};
class FileLocation;
enum ClipState {
ClipReading,
ClipError,
};
struct ClipFrameRequest {
ClipFrameRequest() : factor(0), framew(0), frameh(0), outerw(0), outerh(0), rounded(false) {
}
bool valid() const {
return factor > 0;
}
int32 factor;
int32 framew, frameh;
int32 outerw, outerh;
bool rounded;
};
enum ClipReaderNotification {
ClipReaderReinit,
ClipReaderRepaint,
};
enum ClipReaderSteps {
WaitingForDimensionsStep = -3, // before ClipReaderPrivate read the first image and got the original frame size
WaitingForRequestStep = -2, // before ClipReader got the original frame size and prepared the frame request
WaitingForFirstFrameStep = -1, // before ClipReaderPrivate got the frame request and started waiting for the 1-2 delay
};
class ClipReaderPrivate;
class ClipReader {
public:
using Callback = Function<void, ClipReaderNotification>;
ClipReader(const FileLocation &location, const QByteArray &data, Callback &&callback);
static void callback(ClipReader *reader, int32 threadIndex, ClipReaderNotification notification); // reader can be deleted
void setAutoplay() {
_autoplay = true;
}
bool autoplay() const {
return _autoplay;
}
void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded);
QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh, uint64 ms);
QPixmap frameOriginal() const {
Frame *frame = frameToShow();
if (!frame) return QPixmap();
QPixmap result(frame ? QPixmap::fromImage(frame->original) : QPixmap());
result.detach();
return result;
}
bool currentDisplayed() const {
Frame *frame = frameToShow();
return frame ? (frame->displayed.loadAcquire() != 0) : true;
}
bool paused() const {
return _paused.loadAcquire();
}
int32 threadIndex() const {
return _threadIndex;
}
int32 width() const;
int32 height() const;
ClipState state() const;
bool started() const {
int32 step = _step.loadAcquire();
return (step == WaitingForFirstFrameStep) || (step >= 0);
}
bool ready() const;
void stop();
void error();
~ClipReader();
private:
Callback _callback;
ClipState _state;
mutable int32 _width, _height;
mutable QAtomicInt _step; // -2, -1 - init, 0-5 - work, show ((state + 1) / 2) % 3 state, write ((state + 3) / 2) % 3
struct Frame {
Frame() : displayed(false) {
}
void clear() {
pix = QPixmap();
original = QImage();
}
QPixmap pix;
QImage original;
ClipFrameRequest request;
QAtomicInt displayed;
};
mutable Frame _frames[3];
Frame *frameToShow(int32 *index = 0) const; // 0 means not ready
Frame *frameToWrite(int32 *index = 0) const; // 0 means not ready
Frame *frameToWriteNext(bool check, int32 *index = 0) const;
void moveToNextShow() const;
void moveToNextWrite() const;
QAtomicInt _paused;
int32 _threadIndex;
bool _autoplay;
friend class ClipReadManager;
ClipReaderPrivate *_private;
};
static ClipReader * const BadClipReader = SharedMemoryLocation<ClipReader, 0>();
enum ClipProcessResult {
ClipProcessError,
ClipProcessStarted,
ClipProcessPaused,
ClipProcessRepaint,
ClipProcessCopyFrame,
ClipProcessWait,
};
class ClipReadManager : public QObject {
Q_OBJECT
public:
ClipReadManager(QThread *thread);
int32 loadLevel() const {
return _loadLevel.load();
}
void append(ClipReader *reader, const FileLocation &location, const QByteArray &data);
void start(ClipReader *reader);
void update(ClipReader *reader);
void stop(ClipReader *reader);
bool carries(ClipReader *reader) const;
~ClipReadManager();
signals:
void processDelayed();
void callback(ClipReader *reader, qint32 threadIndex, qint32 notification);
public slots:
void process();
void finish();
private:
void clear();
QAtomicInt _loadLevel;
struct MutableAtomicInt {
MutableAtomicInt(int value) : v(value) {
}
mutable QAtomicInt v;
};
typedef QMap<ClipReader*, MutableAtomicInt> ReaderPointers;
ReaderPointers _readerPointers;
mutable QReadWriteLock _readerPointersMutex;
ReaderPointers::const_iterator constUnsafeFindReaderPointer(ClipReaderPrivate *reader) const;
ReaderPointers::iterator unsafeFindReaderPointer(ClipReaderPrivate *reader);
bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms);
enum ResultHandleState {
ResultHandleRemove,
ResultHandleStop,
ResultHandleContinue,
};
ResultHandleState handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms);
typedef QMap<ClipReaderPrivate*, uint64> Readers;
Readers _readers;
QTimer _timer;
QThread *_processingInThread;
bool _needReProcess;
};
MTPDocumentAttribute clipReadAnimatedAttributes(const QString &fname, const QByteArray &data, QImage &cover);

View file

@ -371,6 +371,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_media_clip_reader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_overviewwidget.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@ -701,6 +705,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_media_clip_reader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_overviewwidget.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@ -1062,6 +1070,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_media_clip_reader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_overviewwidget.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@ -1250,6 +1262,10 @@
<ClCompile Include="SourceFiles\main.cpp" />
<ClCompile Include="SourceFiles\mainwidget.cpp" />
<ClCompile Include="SourceFiles\mediaview.cpp" />
<ClCompile Include="SourceFiles\media\media_clip_ffmpeg.cpp" />
<ClCompile Include="SourceFiles\media\media_clip_implementation.cpp" />
<ClCompile Include="SourceFiles\media\media_clip_qtgif.cpp" />
<ClCompile Include="SourceFiles\media\media_clip_reader.cpp" />
<ClCompile Include="SourceFiles\mtproto\auth_key.cpp" />
<ClCompile Include="SourceFiles\mtproto\connection.cpp" />
<ClCompile Include="SourceFiles\mtproto\connection_abstract.cpp" />
@ -1508,6 +1524,23 @@
<ClInclude Include="SourceFiles\inline_bots\inline_bot_layout_item.h" />
<ClInclude Include="SourceFiles\inline_bots\inline_bot_result.h" />
<ClInclude Include="SourceFiles\inline_bots\inline_bot_send_data.h" />
<CustomBuild Include="SourceFiles\media\media_clip_reader.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">Moc%27ing media_clip_reader.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing media_clip_reader.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing media_clip_reader.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/media/media_clip_reader.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
</CustomBuild>
<ClInclude Include="SourceFiles\media\media_clip_ffmpeg.h" />
<ClInclude Include="SourceFiles\media\media_clip_implementation.h" />
<ClInclude Include="SourceFiles\media\media_clip_qtgif.h" />
<ClInclude Include="SourceFiles\mtproto\auth_key.h" />
<CustomBuild Include="SourceFiles\mtproto\connection.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>

View file

@ -112,6 +112,9 @@
<Filter Include="SourceFiles\platform\winrt">
<UniqueIdentifier>{385d4cd5-f702-41b7-9e39-707d16b118d5}</UniqueIdentifier>
</Filter>
<Filter Include="SourceFiles\media">
<UniqueIdentifier>{a281888a-8b70-4e95-9b03-ebcb02837df4}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="SourceFiles\main.cpp">
@ -1344,6 +1347,27 @@
<ClCompile Include="SourceFiles\platform\linux\linux_libs.cpp">
<Filter>SourceFiles\platform\linux</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_media_clip_reader.cpp">
<Filter>GeneratedFiles\Deploy</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_media_clip_reader.cpp">
<Filter>GeneratedFiles\Debug</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_media_clip_reader.cpp">
<Filter>GeneratedFiles\Release</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\media\media_clip_reader.cpp">
<Filter>SourceFiles\media</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\media\media_clip_implementation.cpp">
<Filter>SourceFiles\media</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\media\media_clip_ffmpeg.cpp">
<Filter>SourceFiles\media</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\media\media_clip_qtgif.cpp">
<Filter>SourceFiles\media</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="SourceFiles\stdafx.h">
@ -1595,6 +1619,15 @@
<ClInclude Include="SourceFiles\platform\linux\linux_libs.h">
<Filter>SourceFiles\platform\linux</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\media\media_clip_implementation.h">
<Filter>SourceFiles\media</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\media\media_clip_ffmpeg.h">
<Filter>SourceFiles\media</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\media\media_clip_qtgif.h">
<Filter>SourceFiles\media</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="SourceFiles\application.h">
@ -1885,6 +1918,9 @@
<CustomBuild Include="SourceFiles\ui\inner_dropdown.h">
<Filter>SourceFiles\ui</Filter>
</CustomBuild>
<CustomBuild Include="SourceFiles\media\media_clip_reader.h">
<Filter>SourceFiles\media</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<None Include="Resources\langs\lang_it.strings">