Support round video inline playback with sound.

This commit is contained in:
John Preston 2017-04-02 19:42:18 +03:00
parent cbf040b4dc
commit 5480a63beb
12 changed files with 122 additions and 50 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

View File

@ -418,3 +418,7 @@ msgWaveformBar: 2px;
msgWaveformSkip: 1px;
msgWaveformMin: 2px;
msgWaveformMax: 20px;
historyVideoMessageMute: icon {{ "volume_mute", historyFileThumbIconFg }};
historyVideoMessageMuteSelected: icon {{ "volume_mute", historyFileThumbIconFgSelected }};
historyVideoMessageMuteSize: 25px;

View File

@ -892,17 +892,20 @@ void HistoryItem::clipCallback(Media::Clip::Notification notification) {
switch (notification) {
case NotificationReinit: {
bool stopped = false;
auto stopped = false;
if (reader->autoPausedGif()) {
if (MainWidget *m = App::main()) {
if (auto m = App::main()) {
if (!m->isItemVisible(this)) { // stop animation if it is not visible
media->stopInline();
if (DocumentData *document = media->getDocument()) { // forget data from memory
if (auto document = media->getDocument()) { // forget data from memory
document->forget();
}
stopped = true;
}
}
} else if (reader->mode() == Media::Clip::Reader::Mode::Video && reader->state() == Media::Clip::State::Finished) {
// Stop finished video message.
media->stopInline();
}
if (!stopped) {
setPendingInitDimensions();

View File

@ -1653,7 +1653,7 @@ HistoryGif::HistoryGif(HistoryItem *parent, DocumentData *document, const QStrin
setStatusSize(FileStatusSizeReady);
if (!caption.isEmpty()) {
if (!caption.isEmpty() && !_data->isRoundVideo()) {
_caption.setText(st::messageTextStyle, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent));
}
@ -1681,8 +1681,7 @@ void HistoryGif::initDimensions() {
if (!_gif->autoplay()) {
Ui::show(Box<InformBox>(lang(lng_gif_error)));
}
App::unregGifItem(_gif.get());
_gif.setBad();
setClipReader(Media::Clip::ReaderPointer::Bad());
}
if (_gif && _gif->ready()) {
@ -1797,10 +1796,12 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return;
_data->automaticLoad(_parent);
bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading();
bool selected = (selection == FullSelection);
auto loaded = _data->loaded();
auto displayLoading = (_parent->id < 0) || _data->displayLoading();
auto selected = (selection == FullSelection);
if (loaded && !_gif && !_gif.isBad() && cAutoPlayGif()) {
auto videoFinished = _gif && (_gif->mode() == Media::Clip::Reader::Mode::Video) && (_gif->state() == Media::Clip::State::Finished);
if (loaded && cAutoPlayGif() && ((!_gif && !_gif.isBad()) || videoFinished)) {
Ui::autoplayMediaInlineAsync(_parent->fullId());
}
@ -1811,6 +1812,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
auto captionw = width - st::msgPadding.left() - st::msgPadding.right();
auto isRound = _data->isRoundVideo();
auto displayMute = false;
auto animating = (_gif && _gif->started());
if (!animating || _parent->id < 0) {
@ -1848,6 +1850,13 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
| ((isBubbleBottom() && _caption.isEmpty()) ? (ImageRoundCorner::BottomLeft | ImageRoundCorner::BottomRight) : ImageRoundCorner::None));
if (animating) {
auto paused = App::wnd()->controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
if (isRound) {
if (_gif->mode() == Media::Clip::Reader::Mode::Video) {
paused = false;
} else {
displayMute = true;
}
}
p.drawPixmap(rthumb.topLeft(), _gif->current(_thumbw, _thumbh, width, height, roundRadius, roundCorners, paused ? 0 : ms));
} else {
p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, _thumbh, width, height, roundRadius, roundCorners));
@ -1857,8 +1866,8 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
}
if (radial || _gif.isBad() || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif()))) {
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);
auto radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1.;
auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(st::msgDateImgBgSelected);
@ -1908,13 +1917,21 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
p.drawTextLeft(statusX, statusY, _width, _statusText, statusW - 2 * st::msgDateImgPadding.x());
}
}
if (displayMute) {
auto muteRect = rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + rthumb.height() - st::msgDateImgDelta - st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, _width);
p.setPen(Qt::NoPen);
p.setBrush(selected ? st::msgDateImgBgSelected : st::msgDateImgBg);
PainterHighQualityEnabler hq(p);
p.drawEllipse(muteRect);
(selected ? st::historyVideoMessageMuteSelected : st::historyVideoMessageMute).paintInCenter(p, muteRect);
}
if (isRound) {
auto mediaUnread = _parent->isMediaUnread();
auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
auto statusX = skipx + st::msgDateImgDelta + st::msgDateImgPadding.x();
auto statusY = skipy + height - st::msgDateImgDelta - statusH;
auto statusY = skipy + height - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y();
if (_parent->isMediaUnread()) {
statusW += st::mediaUnreadSkip + st::mediaUnreadSize;
}
@ -1970,7 +1987,7 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) {
if (_data->uploading()) {
result.link = _cancell;
} else if (!_gif || !cAutoPlayGif()) {
} else if (!_gif || !cAutoPlayGif() || _data->isRoundVideo()) {
result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel);
}
if (_parent->getMedia() == this) {
@ -2047,35 +2064,56 @@ ImagePtr HistoryGif::replyPreview() {
}
bool HistoryGif::playInline(bool autoplay) {
using Mode = Media::Clip::Reader::Mode;
if (_data->isRoundVideo() && _gif) {
// Stop autoplayed silent video when we start playback by click.
// Stop finished video message when autoplay starts.
if ((!autoplay && _gif->mode() == Mode::Gif)
|| (autoplay && _gif->mode() == Mode::Video && _gif->state() == Media::Clip::State::Finished)) {
stopInline();
}
}
if (_gif) {
stopInline();
} else if (_data->loaded(DocumentData::FilePathResolveChecked)) {
if (!cAutoPlayGif()) {
App::stopGifItems();
}
_gif = Media::Clip::MakeReader(_data->location(), _data->data(), [this](Media::Clip::Notification notification) {
auto mode = (!autoplay && _data->isRoundVideo()) ? Mode::Video : Mode::Gif;
setClipReader(Media::Clip::MakeReader(_data->location(), _data->data(), [this](Media::Clip::Notification notification) {
_parent->clipCallback(notification);
});
App::regGifItem(_gif.get(), _parent);
if (_gif) _gif->setAutoplay();
}, mode));
if (mode == Mode::Video) {
if (App::main()) {
App::main()->mediaMarkRead(_data);
}
}
if (_gif && autoplay) {
_gif->setAutoplay();
}
}
return true;
}
void HistoryGif::stopInline() {
if (_gif) {
App::unregGifItem(_gif.get());
}
_gif.reset();
clearClipReader();
_parent->setPendingInitDimensions();
Notify::historyItemLayoutChanged(_parent);
}
HistoryGif::~HistoryGif() {
void HistoryGif::setClipReader(Media::Clip::ReaderPointer gif) {
if (_gif) {
App::unregGifItem(_gif.get());
}
_gif = std::move(gif);
if (_gif) {
App::regGifItem(_gif.get(), _parent);
}
}
HistoryGif::~HistoryGif() {
clearClipReader();
}
float64 HistoryGif::dataProgress() const {
@ -2099,10 +2137,6 @@ HistorySticker::HistorySticker(HistoryItem *parent, DocumentData *document) : Hi
}
}
class TestClickHandler : public ClickHandler {
};
void HistorySticker::initDimensions() {
auto sticker = _data->sticker();

View File

@ -515,6 +515,9 @@ public:
return _caption.originalTextWithEntities();
}
bool needsBubble() const override {
if (_data->isRoundVideo()) {
return false;
}
if (!_caption.isEmpty()) {
return true;
}
@ -546,6 +549,11 @@ protected:
bool dataFinished() const override;
bool dataLoaded() const override;
void setClipReader(Media::Clip::ReaderPointer gif);
void clearClipReader() {
setClipReader(Media::Clip::ReaderPointer());
}
private:
gsl::not_null<DocumentData*> _data;
int32 _thumbw = 1;

View File

@ -54,7 +54,10 @@ MediaOverviewType messageMediaToOverviewType(HistoryMedia *media) {
case MediaTypeFile: return OverviewFiles;
case MediaTypeMusicFile: return media->getDocument()->isMusic() ? OverviewMusicFiles : OverviewCount;
case MediaTypeVoiceFile: return OverviewVoiceFiles;
case MediaTypeGif: return media->getDocument()->isGifv() ? OverviewCount : OverviewFiles;
case MediaTypeGif: {
auto document = media->getDocument();
return (document->isGifv() || document->isRoundVideo()) ? OverviewCount : OverviewFiles;
} break;
default: break;
}
return OverviewCount;

View File

@ -338,8 +338,8 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
_gif.setBad();
getShownDocument()->forget();
} else if (_gif->ready() && !_gif->started()) {
int32 height = st::inlineMediaHeight;
QSize frame = countFrameSize();
auto height = st::inlineMediaHeight;
auto frame = countFrameSize();
_gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, ImageRoundCorner::None);
} else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
_gif.reset();

View File

@ -222,11 +222,12 @@ bool MediaView::fileBubbleShown() const {
bool MediaView::gifShown() const {
if (_gif && _gif->ready()) {
if (!_gif->started()) {
if (_doc->isVideo() && _autoplayVideoDocument != _doc && !_gif->videoPaused()) {
if ((_doc->isVideo() || _doc->isRoundVideo()) && _autoplayVideoDocument != _doc && !_gif->videoPaused()) {
_gif->pauseResumeVideo();
const_cast<MediaView*>(this)->_videoPaused = _gif->videoPaused();
}
_gif->start(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), ImageRoundRadius::None, ImageRoundCorner::None);
auto rounding = _doc->isRoundVideo() ? ImageRoundRadius::Ellipse : ImageRoundRadius::None;
_gif->start(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, ImageRoundCorner::All);
const_cast<MediaView*>(this)->_current = QPixmap();
}
return true;// _gif->state() != Media::Clip::State::Error;
@ -522,7 +523,7 @@ void MediaView::step_radial(TimeMs ms, bool timer) {
update(radialRect());
}
if (_doc && _doc->loaded() && _doc->size < App::kImageSizeLimit && (!_radial.animating() || _doc->isAnimation() || _doc->isVideo())) {
if (_doc->isVideo()) {
if (_doc->isVideo() || _doc->isRoundVideo()) {
_autoplayVideoDocument = _doc;
}
if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) {
@ -828,7 +829,7 @@ void MediaView::clipCallback(Media::Clip::Notification notification) {
_videoStopped = true;
updateSilentVideoPlaybackState();
} else {
_videoIsSilent = _doc->isVideo() && !_gif->hasAudio();
_videoIsSilent = (_doc->isVideo() || _doc->isRoundVideo()) && !_gif->hasAudio();
_videoDurationMs = _gif->getDurationMs();
_videoPositionMs = _gif->getPositionMs();
if (_videoIsSilent) {
@ -1140,7 +1141,7 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) {
_overview = doc->isVideo() ? OverviewVideos : OverviewFiles;
findCurrent();
}
if (doc->isVideo()) {
if (doc->isVideo() || doc->isRoundVideo()) {
_autoplayVideoDocument = doc;
}
displayDocument(doc, context);
@ -1383,6 +1384,14 @@ void MediaView::displayFinished() {
}
}
Images::Options MediaView::videoThumbOptions() const {
auto options = Images::Option::Smooth | Images::Option::Blurred;
if (_doc->isRoundVideo()) {
options |= Images::Option::Circled;
}
return options;
}
void MediaView::initAnimation() {
Expects(_doc != nullptr);
Expects(_doc->isAnimation() || _doc->isVideo());
@ -1394,12 +1403,12 @@ void MediaView::initAnimation() {
createClipReader();
location.accessDisable();
} else if (_doc->dimensions.width() && _doc->dimensions.height()) {
int w = _doc->dimensions.width();
int h = _doc->dimensions.height();
_current = _doc->thumb->pixNoCache(w, h, Images::Option::Smooth | Images::Option::Blurred, w / cIntRetinaFactor(), h / cIntRetinaFactor());
auto w = _doc->dimensions.width();
auto h = _doc->dimensions.height();
_current = _doc->thumb->pixNoCache(w, h, videoThumbOptions(), w / cIntRetinaFactor(), h / cIntRetinaFactor());
if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
} else {
_current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), Images::Option::Smooth | Images::Option::Blurred, st::mediaviewFileIconSize, st::mediaviewFileIconSize);
_current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), videoThumbOptions(), st::mediaviewFileIconSize, st::mediaviewFileIconSize);
}
}
@ -1412,12 +1421,12 @@ void MediaView::createClipReader() {
if (_doc->dimensions.width() && _doc->dimensions.height()) {
int w = _doc->dimensions.width();
int h = _doc->dimensions.height();
_current = _doc->thumb->pixNoCache(w, h, Images::Option::Smooth | Images::Option::Blurred, w / cIntRetinaFactor(), h / cIntRetinaFactor());
_current = _doc->thumb->pixNoCache(w, h, videoThumbOptions(), w / cIntRetinaFactor(), h / cIntRetinaFactor());
if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
} else {
_current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), Images::Option::Smooth | Images::Option::Blurred, st::mediaviewFileIconSize, st::mediaviewFileIconSize);
_current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), videoThumbOptions(), st::mediaviewFileIconSize, st::mediaviewFileIconSize);
}
auto mode = _doc->isVideo() ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif;
auto mode = (_doc->isVideo() || _doc->isRoundVideo()) ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif;
_gif = std::make_unique<Media::Clip::Reader>(_doc->location(), _doc->data(), [this](Media::Clip::Notification notification) {
clipCallback(notification);
}, mode);
@ -1475,7 +1484,7 @@ void MediaView::initThemePreview() {
}
void MediaView::createClipController() {
if (!_doc->isVideo()) return;
if (!_doc->isVideo() && !_doc->isRoundVideo()) return;
_clipController.create(this);
setClipControllerGeometry();
@ -1530,7 +1539,8 @@ void MediaView::restartVideoAtSeekPosition(TimeMs positionMs) {
_autoplayVideoDocument = _doc;
if (_current.isNull()) {
_current = _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), ImageRoundRadius::None, ImageRoundCorner::None, getms());
auto rounding = _doc->isRoundVideo() ? ImageRoundRadius::Ellipse : ImageRoundRadius::None;
_current = _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, ImageRoundCorner::All, getms());
}
_gif = std::make_unique<Media::Clip::Reader>(_doc->location(), _doc->data(), [this](Media::Clip::Notification notification) {
clipCallback(notification);
@ -1638,6 +1648,9 @@ void MediaView::paintEvent(QPaintEvent *e) {
for (int i = 0, l = region.rectCount(); i < l; ++i) {
p.fillRect(rs.at(i), st::mediaviewVideoBg);
}
if (_doc->isRoundVideo()) {
p.setCompositionMode(m);
}
} else {
for (int i = 0, l = region.rectCount(); i < l; ++i) {
p.fillRect(rs.at(i), st::mediaviewBg);
@ -1670,7 +1683,8 @@ void MediaView::paintEvent(QPaintEvent *e) {
if (_photo || fileShown()) {
QRect imgRect(_x, _y, _w, _h);
if (imgRect.intersects(r)) {
auto toDraw = _current.isNull() ? _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), ImageRoundRadius::None, ImageRoundCorner::None, ms) : _current;
auto rounding = _doc->isRoundVideo() ? ImageRoundRadius::Ellipse : ImageRoundRadius::None;
auto toDraw = _current.isNull() ? _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, ImageRoundCorner::None, ms) : _current;
if (!_gif && (!_doc || !_doc->sticker() || _doc->sticker()->img->isNull()) && toDraw.hasAlpha()) {
p.fillRect(imgRect, _transparentBrush);
}
@ -2011,7 +2025,7 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) {
if (_doc && !_doc->loading() && (fileBubbleShown() || !_doc->loaded())) {
onDocClick();
} else if (_doc && _doc->isVideo()) {
} else if (_doc && (_doc->isVideo() || _doc->isRoundVideo())) {
onVideoPauseResume();
}
} else if (e->key() == Qt::Key_Left) {
@ -2463,7 +2477,7 @@ void MediaView::updateOver(QPoint pos) {
} else if (_closeNav.contains(pos)) {
updateOverState(OverClose);
} else if (_doc && fileShown() && QRect(_x, _y, _w, _h).contains(pos)) {
if (_doc->isVideo() && _gif) {
if ((_doc->isVideo() || _doc->isRoundVideo()) && _gif) {
updateOverState(OverVideo);
} else if (!_doc->loaded()) {
updateOverState(OverIcon);

View File

@ -178,6 +178,7 @@ private:
void initAnimation();
void createClipReader();
Images::Options videoThumbOptions() const;
void initThemePreview();
void destroyThemePreview();

View File

@ -1205,14 +1205,14 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context,
} else if (data->size < App::kImageSizeLimit) {
if (!data->data().isEmpty() && playAnimation) {
if (action == ActionOnLoadPlayInline && context && context->getMedia()) {
context->getMedia()->playInline(context);
context->getMedia()->playInline();
} else {
App::wnd()->showDocument(data, context);
}
} else if (location.accessEnable()) {
if (data->isAnimation() || QImageReader(location.name()).canRead()) {
if (action == ActionOnLoadPlayInline && context && context->getMedia()) {
context->getMedia()->playInline(context);
context->getMedia()->playInline();
} else {
App::wnd()->showDocument(data, context);
}
@ -1485,7 +1485,7 @@ void DocumentData::performActionOnLoad() {
} else if (playAnimation) {
if (loaded()) {
if (_actionOnLoad == ActionOnLoadPlayInline && item->getMedia()) {
item->getMedia()->playInline(item);
item->getMedia()->playInline();
} else {
App::wnd()->showDocument(this, item);
}
@ -1504,7 +1504,7 @@ void DocumentData::performActionOnLoad() {
} else if (loc.accessEnable()) {
if (showImage && QImageReader(loc.name()).canRead()) {
if (_actionOnLoad == ActionOnLoadPlayInline && item && item->getMedia()) {
item->getMedia()->playInline(item);
item->getMedia()->playInline();
} else {
App::wnd()->showDocument(this, item);
}

View File

@ -68,6 +68,11 @@ public:
explicit operator bool() const {
return valid();
}
static inline ReaderPointer Bad() {
ReaderPointer result;
result.setBad();
return result;
}
~ReaderPointer();
private: