Allow seek in PiP.

This commit is contained in:
John Preston 2020-02-03 19:48:25 +04:00
parent 23388b5705
commit d6e989cad5
3 changed files with 153 additions and 23 deletions

View File

@ -288,6 +288,7 @@ pipBorderSnapArea: 16px;
pipResizeArea: 10px; pipResizeArea: 10px;
pipControlSkip: 6px; pipControlSkip: 6px;
pipPlaybackWidth: 2px; pipPlaybackWidth: 2px;
pipPlaybackWide: 4px;
pipPlaybackSkip: 4px; pipPlaybackSkip: 4px;
pipPlayIcon: icon {{ "player_pip_play", mediaviewPipControlsFg }}; pipPlayIcon: icon {{ "player_pip_play", mediaviewPipControlsFg }};
pipPlayIconOver: icon {{ "player_pip_play", mediaviewPipControlsFgOver }}; pipPlayIconOver: icon {{ "player_pip_play", mediaviewPipControlsFgOver }};

View File

@ -306,6 +306,13 @@ RectParts PipPanel::attached() const {
return _attached; return _attached;
} }
void PipPanel::setDragDisabled(bool disabled) {
_dragDisabled = disabled;
if (_dragState) {
_dragState = std::nullopt;
}
}
bool PipPanel::dragging() const { bool PipPanel::dragging() const {
return _dragState.has_value(); return _dragState.has_value();
} }
@ -559,7 +566,8 @@ void PipPanel::mouseMoveEvent(QMouseEvent *e) {
const auto point = e->globalPos(); const auto point = e->globalPos();
const auto distance = QApplication::startDragDistance(); const auto distance = QApplication::startDragDistance();
if (!_dragState if (!_dragState
&& (point - _pressPoint).manhattanLength() > distance) { && (point - _pressPoint).manhattanLength() > distance
&& !_dragDisabled) {
_dragState = _pressState; _dragState = _pressState;
updateDecorations(); updateDecorations();
_dragStartGeometry = geometry().marginsRemoved(_padding); _dragStartGeometry = geometry().marginsRemoved(_padding);
@ -744,20 +752,26 @@ void Pip::setupPanel() {
_panel.events( _panel.events(
) | rpl::start_with_next([=](not_null<QEvent*> e) { ) | rpl::start_with_next([=](not_null<QEvent*> e) {
const auto mousePosition = [&] {
return static_cast<QMouseEvent*>(e.get())->pos();
};
const auto mouseButton = [&] {
return static_cast<QMouseEvent*>(e.get())->button();
};
switch (e->type()) { switch (e->type()) {
case QEvent::Close: handleClose(); break; case QEvent::Close: handleClose(); break;
case QEvent::Leave: handleLeave(); break; case QEvent::Leave: handleLeave(); break;
case QEvent::MouseMove: case QEvent::MouseMove:
handleMouseMove(static_cast<QMouseEvent*>(e.get())->pos()); handleMouseMove(mousePosition());
break; break;
case QEvent::MouseButtonPress: case QEvent::MouseButtonPress:
handleMousePress(static_cast<QMouseEvent*>(e.get())->button()); handleMousePress(mousePosition(), mouseButton());
break; break;
case QEvent::MouseButtonRelease: case QEvent::MouseButtonRelease:
handleMouseRelease(static_cast<QMouseEvent*>(e.get())->button()); handleMouseRelease(mousePosition(), mouseButton());
break; break;
case QEvent::MouseButtonDblClick: case QEvent::MouseButtonDblClick:
handleDoubleClick(static_cast<QMouseEvent*>(e.get())->button()); handleDoubleClick(mouseButton());
break; break;
} }
}, _panel.lifetime()); }, _panel.lifetime());
@ -775,6 +789,7 @@ void Pip::handleLeave() {
void Pip::handleMouseMove(QPoint position) { void Pip::handleMouseMove(QPoint position) {
setOverState(computeState(position)); setOverState(computeState(position));
seekUpdate(position);
} }
void Pip::setOverState(OverState state) { void Pip::setOverState(OverState state) {
@ -792,8 +807,32 @@ void Pip::setOverState(OverState state) {
st::fadeWrapDuration, st::fadeWrapDuration,
anim::linear); anim::linear);
} }
if (!_pressed) {
updateActiveState(was);
}
_panel.update();
}
void Pip::setPressedState(std::optional<OverState> state) {
if (_pressed == state) {
return;
}
const auto was = activeState();
_pressed = state;
updateActiveState(was);
}
Pip::OverState Pip::activeState() const {
return _pressed.value_or(_over);
}
float64 Pip::activeValue(const Button &button) const {
return button.active.value((activeState() == button.state) ? 1. : 0.);
}
void Pip::updateActiveState(OverState was) {
const auto check = [&](Button &button) { const auto check = [&](Button &button) {
const auto now = (_over == button.state); const auto now = (activeState() == button.state);
if ((was == button.state) != now) { if ((was == button.state) != now) {
button.active.start( button.active.start(
[=, &button] { _panel.update(button.icon); }, [=, &button] { _panel.update(button.icon); },
@ -806,22 +845,31 @@ void Pip::setOverState(OverState state) {
check(_close); check(_close);
check(_enlarge); check(_enlarge);
check(_play); check(_play);
_panel.update(); check(_playback);
} }
void Pip::handleMousePress(Qt::MouseButton button) { void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
if (button != Qt::LeftButton) { if (button != Qt::LeftButton) {
return; return;
} }
_pressed = _over; _pressed = _over;
if (_over == OverState::Playback) {
_panel.setDragDisabled(true);
}
seekUpdate(position);
} }
void Pip::handleMouseRelease(Qt::MouseButton button) { void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
if (button != Qt::LeftButton) {
return;
}
seekUpdate(position);
const auto pressed = base::take(_pressed); const auto pressed = base::take(_pressed);
if (button != Qt::LeftButton if (pressed && *pressed == OverState::Playback) {
|| _panel.dragging() _panel.setDragDisabled(false);
|| !pressed seekFinish(_playbackProgress->value());
|| *pressed != _over) { return;
} else if (_panel.dragging() || !pressed || *pressed != _over) {
_lastHandledPress = std::nullopt; _lastHandledPress = std::nullopt;
return; return;
} }
@ -844,6 +892,51 @@ void Pip::handleDoubleClick(Qt::MouseButton button) {
_closeAndContinue(); _closeAndContinue();
} }
void Pip::seekUpdate(QPoint position) {
if (!_pressed || *_pressed != OverState::Playback) {
return;
}
const auto unbound = (position.x() - _playback.icon.x())
/ float64(_playback.icon.width());
const auto progress = std::clamp(unbound, 0., 1.);
seekProgress(progress);
}
void Pip::seekProgress(float64 value) {
if (!_lastDurationMs) {
return;
}
_playbackProgress->setValue(value, false);
const auto positionMs = std::clamp(
static_cast<crl::time>(value * _lastDurationMs),
crl::time(0),
_lastDurationMs);
if (_seekPositionMs != positionMs) {
_seekPositionMs = positionMs;
if (!_instance.player().paused()
&& !_instance.player().finished()) {
_pausedBySeek = true;
playbackPauseResume();
}
}
}
void Pip::seekFinish(float64 value) {
if (!_lastDurationMs) {
return;
}
const auto positionMs = std::clamp(
static_cast<crl::time>(value * _lastDurationMs),
crl::time(0),
_lastDurationMs);
_seekPositionMs = -1;
_startPaused = !_pausedBySeek && !_instance.player().finished();
restartAtSeekPosition(positionMs);
}
void Pip::setupButtons() { void Pip::setupButtons() {
_close.state = OverState::Close; _close.state = OverState::Close;
_enlarge.state = OverState::Enlarge; _enlarge.state = OverState::Enlarge;
@ -883,7 +976,7 @@ void Pip::setupButtons() {
st::pipPlayIcon.width(), st::pipPlayIcon.width(),
st::pipPlayIcon.height()); st::pipPlayIcon.height());
const auto playbackSkip = st::pipPlaybackSkip; const auto playbackSkip = st::pipPlaybackSkip;
const auto playbackHeight = 2 * playbackSkip + st::pipPlaybackWidth; const auto playbackHeight = 2 * playbackSkip + st::pipPlaybackWide;
_playback.area = QRect( _playback.area = QRect(
rect.x(), rect.x(),
rect.y() + rect.height() - playbackHeight, rect.y() + rect.height() - playbackHeight,
@ -975,8 +1068,7 @@ void Pip::paintButtons(QPainter &p) const {
const Button &button, const Button &button,
const style::icon &icon, const style::icon &icon,
const style::icon &iconOver) { const style::icon &iconOver) {
const auto over = button.active.value( const auto over = activeValue(button);
(_over == button.state) ? 1. : 0.);
if (over < 1.) { if (over < 1.) {
icon.paint(p, button.icon.x(), button.icon.y(), outer); icon.paint(p, button.icon.x(), button.icon.y(), outer);
} }
@ -996,11 +1088,15 @@ void Pip::paintButtons(QPainter &p) const {
void Pip::paintPlayback(QPainter &p) const { void Pip::paintPlayback(QPainter &p) const {
const auto radius = _playback.icon.height() / 2; const auto radius = _playback.icon.height() / 2;
const auto shown = activeValue(_playback);
const auto progress = _playbackProgress->value(); const auto progress = _playbackProgress->value();
const auto left = _playback.icon.x();
const auto top = _playback.icon.y();
const auto width = _playback.icon.width(); const auto width = _playback.icon.width();
const auto height = _playback.icon.height(); const auto height = anim::interpolate(
st::pipPlaybackWidth,
_playback.icon.height(),
activeValue(_playback));
const auto left = _playback.icon.x();
const auto top = _playback.icon.y() + _playback.icon.height() - height;
const auto done = int(std::round(width * progress)); const auto done = int(std::round(width * progress));
PainterHighQualityEnabler hq(p); PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
@ -1055,9 +1151,23 @@ void Pip::handleStreamingUpdate(Streaming::Update &&update) {
void Pip::updatePlaybackState() { void Pip::updatePlaybackState() {
const auto state = _instance.player().prepareLegacyState(); const auto state = _instance.player().prepareLegacyState();
updatePlayPauseResumeState(state); updatePlayPauseResumeState(state);
if (state.position != kTimeUnknown && state.length != kTimeUnknown) { if (state.position == kTimeUnknown
_playbackProgress->updateState(state); || state.length == kTimeUnknown
|| _pausedBySeek) {
return;
} }
_playbackProgress->updateState(state);
qint64 position = 0, length = state.length;
if (Player::IsStoppedAtEnd(state.state)) {
position = state.length;
} else if (!Player::IsStoppedOrStopping(state.state)) {
position = state.position;
} else {
position = 0;
}
const auto playFrequency = state.frequency;
_lastDurationMs = (state.length * crl::time(1000)) / playFrequency;
} }
void Pip::handleStreamingError(Streaming::Error &&error) { void Pip::handleStreamingError(Streaming::Error &&error) {
@ -1069,6 +1179,7 @@ void Pip::playbackPauseResume() {
_panel.close(); _panel.close();
} else if (_instance.player().finished() } else if (_instance.player().finished()
|| !_instance.player().active()) { || !_instance.player().active()) {
_startPaused = false;
restartAtSeekPosition(0); restartAtSeekPosition(0);
} else if (_instance.player().paused()) { } else if (_instance.player().paused()) {
_instance.resume(); _instance.resume();
@ -1088,6 +1199,10 @@ void Pip::restartAtSeekPosition(crl::time position) {
options.audioId = _instance.player().prepareLegacyState().id; options.audioId = _instance.player().prepareLegacyState().id;
options.speed = _delegate->pipPlaybackSpeed(); options.speed = _delegate->pipPlaybackSpeed();
_instance.play(options); _instance.play(options);
if (_startPaused) {
_instance.pause();
}
_pausedBySeek = false;
updatePlaybackState(); updatePlaybackState();
} }

View File

@ -58,6 +58,7 @@ public:
void setPosition(Position position); void setPosition(Position position);
[[nodiscard]] QRect inner() const; [[nodiscard]] QRect inner() const;
[[nodiscard]] RectParts attached() const; [[nodiscard]] RectParts attached() const;
void setDragDisabled(bool disabled);
[[nodiscard]] bool dragging() const; [[nodiscard]] bool dragging() const;
[[nodiscard]] rpl::producer<> saveGeometryRequests() const; [[nodiscard]] rpl::producer<> saveGeometryRequests() const;
@ -87,6 +88,7 @@ private:
QSize _ratio; QSize _ratio;
bool _useTransparency = true; bool _useTransparency = true;
bool _dragDisabled = false;
style::margins _padding; style::margins _padding;
RectPart _overState = RectPart(); RectPart _overState = RectPart();
@ -163,10 +165,14 @@ private:
const FrameRequest &request) const; const FrameRequest &request) const;
[[nodiscard]] OverState computeState(QPoint position) const; [[nodiscard]] OverState computeState(QPoint position) const;
void setOverState(OverState state); void setOverState(OverState state);
void setPressedState(std::optional<OverState> state);
[[nodiscard]] OverState activeState() const;
[[nodiscard]] float64 activeValue(const Button &button) const;
void updateActiveState(OverState was);
void handleMouseMove(QPoint position); void handleMouseMove(QPoint position);
void handleMousePress(Qt::MouseButton button); void handleMousePress(QPoint position, Qt::MouseButton button);
void handleMouseRelease(Qt::MouseButton button); void handleMouseRelease(QPoint position, Qt::MouseButton button);
void handleDoubleClick(Qt::MouseButton button); void handleDoubleClick(Qt::MouseButton button);
void handleLeave(); void handleLeave();
void handleClose(); void handleClose();
@ -179,6 +185,10 @@ private:
void paintRadialLoadingContent(QPainter &p, const QRect &inner) const; void paintRadialLoadingContent(QPainter &p, const QRect &inner) const;
[[nodiscard]] QRect countRadialRect() const; [[nodiscard]] QRect countRadialRect() const;
void seekUpdate(QPoint position);
void seekProgress(float64 value);
void seekFinish(float64 value);
const not_null<Delegate*> _delegate; const not_null<Delegate*> _delegate;
not_null<DocumentData*> _document; not_null<DocumentData*> _document;
FullMsgId _contextId; FullMsgId _contextId;
@ -188,6 +198,10 @@ private:
std::unique_ptr<PlaybackProgress> _playbackProgress; std::unique_ptr<PlaybackProgress> _playbackProgress;
bool _showPause = false; bool _showPause = false;
bool _startPaused = false;
bool _pausedBySeek = false;
crl::time _seekPositionMs = -1;
crl::time _lastDurationMs = 0;
OverState _over = OverState::None; OverState _over = OverState::None;
std::optional<OverState> _pressed; std::optional<OverState> _pressed;
std::optional<OverState> _lastHandledPress; std::optional<OverState> _lastHandledPress;