First prototype of picture-in-picture player.

This commit is contained in:
John Preston 2020-01-03 14:18:38 +03:00
parent 9feea4a724
commit ca5c9271a3
10 changed files with 411 additions and 23 deletions

View File

@ -672,14 +672,16 @@ PRIVATE
media/streaming/media_streaming_utility.h
media/streaming/media_streaming_video_track.cpp
media/streaming/media_streaming_video_track.h
media/view/media_view_playback_controls.cpp
media/view/media_view_playback_controls.h
media/view/media_view_playback_progress.cpp
media/view/media_view_playback_progress.h
media/view/media_view_group_thumbs.cpp
media/view/media_view_group_thumbs.h
media/view/media_view_overlay_widget.cpp
media/view/media_view_overlay_widget.h
media/view/media_view_pip.cpp
media/view/media_view_pip.h
media/view/media_view_playback_controls.cpp
media/view/media_view_playback_controls.h
media/view/media_view_playback_progress.cpp
media/view/media_view_playback_progress.h
mtproto/config_loader.cpp
mtproto/config_loader.h
mtproto/connection_abstract.cpp

View File

@ -60,6 +60,10 @@ const Information &Document::info() const {
return _info;
}
not_null<DocumentData*> Document::data() const {
return _document;
}
void Document::play(const PlaybackOptions &options) {
_player.play(options);
_info.audio.state.position

View File

@ -31,6 +31,7 @@ public:
[[nodiscard]] Player &player();
[[nodiscard]] const Player &player() const;
[[nodiscard]] const Information &info() const;
[[nodiscard]] not_null<DocumentData*> data() const;
[[nodiscard]] bool waitingShown() const;
[[nodiscard]] float64 waitingOpacity() const;

View File

@ -46,6 +46,10 @@ bool Instance::valid() const {
return (_shared != nullptr);
}
std::shared_ptr<Document> Instance::shared() const {
return _shared;
}
const Player &Instance::player() const {
Expects(_shared != nullptr);

View File

@ -37,6 +37,7 @@ public:
~Instance();
[[nodiscard]] bool valid() const;
[[nodiscard]] std::shared_ptr<Document> shared() const;
[[nodiscard]] const Player &player() const;
[[nodiscard]] const Information &info() const;

View File

@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio.h"
#include "media/view/media_view_playback_controls.h"
#include "media/view/media_view_group_thumbs.h"
#include "media/view/media_view_pip.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "media/player/media_player_instance.h"
@ -186,13 +187,12 @@ struct OverlayWidget::Collage {
};
struct OverlayWidget::Streamed {
template <typename Callback>
Streamed(
not_null<DocumentData*> document,
Data::FileOrigin origin,
QWidget *controlsParent,
not_null<PlaybackControls::Delegate*> controlsDelegate,
Callback &&loadingCallback);
Fn<void()> waitingCallback);
Streaming::Instance instance;
PlaybackControls controls;
@ -204,14 +204,13 @@ struct OverlayWidget::Streamed {
bool resumeOnCallEnd = false;
};
template <typename Callback>
OverlayWidget::Streamed::Streamed(
not_null<DocumentData*> document,
Data::FileOrigin origin,
QWidget *controlsParent,
not_null<PlaybackControls::Delegate*> controlsDelegate,
Callback &&loadingCallback)
: instance(document, origin, std::forward<Callback>(loadingCallback))
Fn<void()> waitingCallback)
: instance(document, origin, std::move(waitingCallback))
, controls(controlsParent, controlsDelegate) {
}
@ -1759,20 +1758,23 @@ void OverlayWidget::showPhoto(not_null<PhotoData*> photo, not_null<PeerData*> co
activateControls();
}
void OverlayWidget::showDocument(not_null<DocumentData*> document, HistoryItem *context) {
showDocument(document, context, Data::CloudTheme());
void OverlayWidget::showDocument(
not_null<DocumentData*> document,
HistoryItem *context) {
showDocument(document, context, Data::CloudTheme(), false);
}
void OverlayWidget::showTheme(
not_null<DocumentData*> document,
const Data::CloudTheme &cloud) {
showDocument(document, nullptr, cloud);
showDocument(document, nullptr, cloud, false);
}
void OverlayWidget::showDocument(
not_null<DocumentData*> document,
HistoryItem *context,
const Data::CloudTheme &cloud) {
const Data::CloudTheme &cloud,
bool continueStreaming) {
if (context) {
setContext(context);
} else {
@ -1783,7 +1785,7 @@ void OverlayWidget::showDocument(
_photo = nullptr;
_streamingStartPaused = false;
displayDocument(document, context, cloud);
displayDocument(document, context, cloud, continueStreaming);
preloadData(0);
activateControls();
}
@ -1845,7 +1847,8 @@ void OverlayWidget::redisplayContent() {
void OverlayWidget::displayDocument(
DocumentData *doc,
HistoryItem *item,
const Data::CloudTheme &cloud) {
const Data::CloudTheme &cloud,
bool continueStreaming) {
if (isHidden()) {
moveToScreen();
}
@ -1870,7 +1873,7 @@ void OverlayWidget::displayDocument(
_doc->dimensions.height());
}
} else {
if (_doc->canBePlayed() && initStreaming()) {
if (_doc->canBePlayed() && initStreaming(continueStreaming)) {
} else if (_doc->isVideoFile()) {
_doc->automaticLoad(fileOrigin(), item);
initStreamingThumbnail();
@ -2001,7 +2004,7 @@ void OverlayWidget::displayFinished() {
}
}
bool OverlayWidget::initStreaming() {
bool OverlayWidget::initStreaming(bool continueStreaming) {
Expects(_doc != nullptr);
Expects(_doc->canBePlayed());
@ -2023,16 +2026,29 @@ bool OverlayWidget::initStreaming() {
handleStreamingError(std::move(error));
}, _streamed->instance.lifetime());
startStreamingPlayer();
if (continueStreaming) {
_pip = nullptr;
}
if (!continueStreaming
|| (!_streamed->instance.player().active()
&& !_streamed->instance.player().finished())) {
startStreamingPlayer();
}
return true;
}
void OverlayWidget::startStreamingPlayer() {
Expects(_streamed != nullptr);
if (!_streamed->withSound && _streamed->instance.player().playing()) {
if (_streamed->instance.player().playing()) {
if (!_streamed->withSound) {
return;
}
_pip = nullptr;
} else if (_pip && _streamed->withSound) {
return;
}
const auto position = _doc
? _doc->session().settings().mediaLastPlaybackPosition(_doc->id)
: 0;
@ -2310,7 +2326,8 @@ void OverlayWidget::playbackPauseResume() {
if (!_doc->canBePlayed() || !initStreaming()) {
redisplayContent();
}
} else if (_streamed->instance.player().finished()) {
} else if (_streamed->instance.player().finished()
|| !_streamed->instance.player().active()) {
_streamingStartPaused = false;
restartAtSeekPosition(0);
} else if (_streamed->instance.player().paused()) {
@ -2338,6 +2355,8 @@ void OverlayWidget::restartAtSeekPosition(crl::time position) {
if (!_streamed->withSound) {
options.mode = Streaming::Mode::Video;
options.loop = true;
} else if (_pip) {
_pip = nullptr;
}
_streamed->instance.play(options);
if (_streamingStartPaused) {
@ -2379,9 +2398,33 @@ float64 OverlayWidget::playbackControlsCurrentVolume() {
return Global::VideoVolume();
}
void OverlayWidget::switchToPip() {
const auto document = _doc;
const auto msgId = _msgid;
_pip = std::make_unique<Pip>(_streamed->instance.shared(), [=] {
showDocument(_doc, Auth().data().message(msgId), {}, true);
});
_pip->move(0, 0);
_pip->show();
_pip->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::Close);
}) | rpl::start_with_next([=] {
crl::on_main(_pip.get(), [=] {
_pip = nullptr;
});
}, _pip->lifetime());
close();
}
void OverlayWidget::playbackToggleFullScreen() {
Expects(_streamed != nullptr);
if (!videoIsGifv() && !_fullScreenVideo) {
switchToPip();
return;
}
if (!videoShown() || (videoIsGifv() && !_fullScreenVideo)) {
return;
}

View File

@ -48,6 +48,7 @@ namespace Media {
namespace View {
class GroupThumbs;
class Pip;
#if defined Q_OS_MAC && !defined OS_MAC_OLD
#define USE_OPENGL_OVERLAY_WIDGET
@ -178,6 +179,7 @@ private:
void playbackPauseOnCall();
void playbackResumeOnCall();
void playbackPauseMusic();
void switchToPip();
void updateOver(QPoint mpos);
void moveToScreen(bool force = false);
@ -239,12 +241,14 @@ private:
void showDocument(
not_null<DocumentData*> document,
HistoryItem *context,
const Data::CloudTheme &cloud);
const Data::CloudTheme &cloud,
bool continueStreaming);
void displayPhoto(not_null<PhotoData*> photo, HistoryItem *item);
void displayDocument(
DocumentData *document,
HistoryItem *item,
const Data::CloudTheme &cloud = Data::CloudTheme());
const Data::CloudTheme &cloud = Data::CloudTheme(),
bool continueStreaming = false);
void displayFinished();
void redisplayContent();
void findCurrent();
@ -258,7 +262,7 @@ private:
void refreshClipControllerGeometry();
void refreshCaptionGeometry();
[[nodiscard]] bool initStreaming();
[[nodiscard]] bool initStreaming(bool continueStreaming = false);
void startStreamingPlayer();
void initStreamingThumbnail();
void streamingReady(Streaming::Information &&info);
@ -374,6 +378,7 @@ private:
bool _blurred = true;
std::unique_ptr<Streamed> _streamed;
std::unique_ptr<Pip> _pip;
const style::icon *_docIcon = nullptr;
style::color _docIconColor;

View File

@ -0,0 +1,264 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/view/media_view_pip.h"
#include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h"
#include "core/application.h"
#include "window/window_controller.h"
#include "styles/style_window.h"
#include <QtGui/QWindow>
#include <QtGui/QScreen>
#include <QtWidgets/QApplication>
namespace Media {
namespace View {
namespace {
constexpr auto kPipLoaderPriority = 2;
} // namespace
Pip::Pip(
std::shared_ptr<Streaming::Document> document,
FnMut<void()> closeAndContinue)
: _instance(document, [=] { waitingAnimationCallback(); })
, _closeAndContinue(std::move(closeAndContinue)) {
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
setupSize();
setupStreaming();
}
void Pip::setupSize() {
_size = style::ConvertScale(_instance.info().video.size);
if (_size.isEmpty()) {
_size = QSize(st::windowMinWidth, st::windowMinHeight);
}
const auto widgetScreen = [&](auto &&widget) -> QScreen* {
if (auto handle = widget ? widget->windowHandle() : nullptr) {
return handle->screen();
}
return nullptr;
};
const auto window = Core::App().activeWindow()
? Core::App().activeWindow()->widget().get()
: nullptr;
const auto activeWindowScreen = widgetScreen(window);
const auto myScreen = widgetScreen(this);
if (activeWindowScreen && myScreen && myScreen != activeWindowScreen) {
windowHandle()->setScreen(activeWindowScreen);
}
const auto screen = activeWindowScreen
? activeWindowScreen
: QGuiApplication::primaryScreen();
const auto available = screen->geometry();
const auto fit = QSize(available.width() / 2, available.height() / 2);
if (_size.width() > fit.width() || _size.height() > fit.height()) {
const auto byHeight = (fit.width() * _size.height() > fit.height() * _size.width());
_size = byHeight
? QSize(_size.width() * fit.height() / _size.height(), fit.height())
: QSize(fit.width(), _size.height() * fit.width() / _size.width());
}
resize(_size);
auto policy = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
policy.setHeightForWidth(true);
setSizePolicy(policy);
}
void Pip::setupStreaming() {
_instance.setPriority(kPipLoaderPriority);
_instance.lockPlayer();
_instance.player().updates(
) | rpl::start_with_next_error([=](Streaming::Update &&update) {
handleStreamingUpdate(std::move(update));
}, [=](Streaming::Error &&error) {
handleStreamingError(std::move(error));
}, _instance.lifetime());
}
void Pip::handleStreamingUpdate(Streaming::Update &&update) {
using namespace Streaming;
update.data.match([&](Information &update) {
setupSize();
}, [&](const PreloadedVideo &update) {
//updatePlaybackState();
}, [&](const UpdateVideo &update) {
this->update();
Core::App().updateNonIdle();
//updatePlaybackState();
}, [&](const PreloadedAudio &update) {
//updatePlaybackState();
}, [&](const UpdateAudio &update) {
//updatePlaybackState();
}, [&](WaitingForData) {
}, [&](MutedByOther) {
}, [&](Finished) {
//updatePlaybackState();
close();
});
}
void Pip::handleStreamingError(Streaming::Error &&error) {
close();
}
void Pip::paintEvent(QPaintEvent *e) {
QPainter p(this);
const auto rect = QRect(QPoint(), size());
const auto image = videoFrameForDirectPaint();
const auto rotation = _instance.info().video.rotation;
const auto rotated = [](QRect rect, int rotation) {
switch (rotation) {
case 0: return rect;
case 90: return QRect(
rect.y(),
-rect.x() - rect.width(),
rect.height(),
rect.width());
case 180: return QRect(
-rect.x() - rect.width(),
-rect.y() - rect.height(),
rect.width(),
rect.height());
case 270: return QRect(
-rect.y() - rect.height(),
rect.x(),
rect.height(),
rect.width());
}
Unexpected("Rotation in OverlayWidget::paintTransformedVideoFrame");
};
PainterHighQualityEnabler hq(p);
if (rotation) {
p.save();
p.rotate(rotation);
}
p.drawImage(rotated(rect, rotation), image);
if (rotation) {
p.restore();
}
if (_instance.player().ready()) {
_instance.markFrameShown();
}
}
void Pip::mousePressEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton) {
return;
}
_pressPoint = e->globalPos();
}
void Pip::mouseReleaseEvent(QMouseEvent *e) {
if (e->button() != Qt::LeftButton || !base::take(_pressPoint)) {
return;
} else if (!base::take(_dragStartPosition)) {
playbackPauseResume();
}
}
void Pip::mouseMoveEvent(QMouseEvent *e) {
if (!_pressPoint) {
return;
}
const auto point = e->globalPos();
const auto distance = QApplication::startDragDistance();
if (!_dragStartPosition
&& (point - *_pressPoint).manhattanLength() > distance) {
_dragStartPosition = pos();
}
if (_dragStartPosition) {
updatePosition(point);
}
}
void Pip::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Space) {
playbackPauseResume();
} else if (e->key() == Qt::Key_Escape) {
crl::on_main(this, [=] {
_closeAndContinue();
});
}
}
void Pip::playbackPauseResume() {
if (_instance.player().finished() || !_instance.player().active()) {
//restartAtSeekPosition(0);
} else if (_instance.player().paused()) {
_instance.resume();
//updatePlaybackState();
//playbackPauseMusic();
} else {
_instance.pause();
//updatePlaybackState();
}
}
void Pip::updatePosition(QPoint point) {
Expects(_dragStartPosition.has_value());
const auto position = *_dragStartPosition + (point - *_pressPoint);
move(position);
}
QImage Pip::videoFrame() const {
return _instance.player().ready()
? _instance.frame(Streaming::FrameRequest())
: _instance.info().video.cover;
}
QImage Pip::videoFrameForDirectPaint() const {
const auto result = videoFrame();
#ifdef USE_OPENGL_OVERLAY_WIDGET
const auto bytesPerLine = result.bytesPerLine();
if (bytesPerLine == result.width() * 4) {
return result;
}
// On macOS 10.8+ we use QOpenGLWidget as OverlayWidget base class.
// The OpenGL painter can't paint textures where byte data is with strides.
// So in that case we prepare a compact copy of the frame to render.
//
// See Qt commit ed557c037847e343caa010562952b398f806adcd
//
auto &cache = _streamed->frameForDirectPaint;
if (cache.size() != result.size()) {
cache = QImage(result.size(), result.format());
}
const auto height = result.height();
const auto line = cache.bytesPerLine();
Assert(line == result.width() * 4);
Assert(line < bytesPerLine);
auto from = result.bits();
auto to = cache.bits();
for (auto y = 0; y != height; ++y) {
memcpy(to, from, line);
to += line;
from += bytesPerLine;
}
return cache;
#endif // USE_OPENGL_OVERLAY_WIDGET
return result;
}
void Pip::waitingAnimationCallback() {
}
} // namespace View
} // namespace Media

View File

@ -0,0 +1,60 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "media/streaming/media_streaming_instance.h"
#include "ui/rp_widget.h"
namespace Media {
namespace View {
#if defined Q_OS_MAC && !defined OS_MAC_OLD
#define USE_OPENGL_OVERLAY_WIDGET
#endif // Q_OS_MAC && !OS_MAC_OLD
#ifdef USE_OPENGL_OVERLAY_WIDGET
using PipParent = Ui::RpWidgetWrap<QOpenGLWidget>;
#else // USE_OPENGL_OVERLAY_WIDGET
using PipParent = Ui::RpWidget;
#endif // USE_OPENGL_OVERLAY_WIDGET
class Pip final : public PipParent {
public:
Pip(
std::shared_ptr<Streaming::Document> document,
FnMut<void()> closeAndContinue);
protected:
void paintEvent(QPaintEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
private:
void setupSize();
void setupStreaming();
void playbackPauseResume();
void updatePosition(QPoint point);
void waitingAnimationCallback();
void handleStreamingUpdate(Streaming::Update &&update);
void handleStreamingError(Streaming::Error &&error);
[[nodiscard]] QImage videoFrame() const;
[[nodiscard]] QImage videoFrameForDirectPaint() const;
Streaming::Instance _instance;
QSize _size;
std::optional<QPoint> _pressPoint;
std::optional<QPoint> _dragStartPosition;
FnMut<void()> _closeAndContinue;
};
} // namespace View
} // namespace Media

View File

@ -207,3 +207,7 @@ themePreviewCancelButton: RoundButton(defaultLightButton) {
}
themePreviewButtonsSkip: 20px;
themePreviewDialogsWidth: 312px;
pipBorderSkip: 20px;
pipBorderSnapArea: 10px;
pip