/* 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_playback_controls.h" #include "media/audio/media_audio.h" #include "media/view/media_view_playback_progress.h" #include "ui/widgets/labels.h" #include "ui/widgets/continuous_sliders.h" #include "ui/effects/fade_animation.h" #include "ui/widgets/buttons.h" #include "lang/lang_keys.h" #include "layout.h" #include "app.h" #include "styles/style_mediaview.h" namespace Media { namespace View { PlaybackControls::PlaybackControls( QWidget *parent, not_null delegate) : RpWidget(parent) , _delegate(delegate) , _playPauseResume(this, st::mediaviewPlayButton) , _playbackSlider(this, st::mediaviewPlayback) , _playbackProgress(std::make_unique()) , _volumeToggle(this, st::mediaviewVolumeToggle) , _volumeController(this, st::mediaviewPlayback) , _speedController(this, st::mediaviewPlayback) , _speedLabel(this, st::mediaviewPlayProgressLabel) , _fullScreenToggle(this, st::mediaviewFullScreenButton) , _pictureInPicture(this, st::mediaviewFullScreenButton) , _playedAlready(this, st::mediaviewPlayProgressLabel) , _toPlayLeft(this, st::mediaviewPlayProgressLabel) , _fadeAnimation(std::make_unique(this)) { _fadeAnimation->show(); _fadeAnimation->setFinishedCallback([=] { fadeFinished(); }); _fadeAnimation->setUpdatedCallback([=](float64 opacity) { fadeUpdated(opacity); }); _pictureInPicture->setIconOverride( &st::mediaviewFullScreenOutIcon, &st::mediaviewFullScreenOutIconOver); _pictureInPicture->addClickHandler([=] { _delegate->playbackControlsToPictureInPicture(); }); _volumeController->setValue(_delegate->playbackControlsCurrentVolume()); _volumeController->setChangeProgressCallback([=](float64 value) { _delegate->playbackControlsVolumeChanged(value); updateVolumeToggleIcon(); }); _volumeController->setChangeFinishedCallback([=](float64) { _delegate->playbackControlsVolumeChangeFinished(); }); updateVolumeToggleIcon(); _volumeToggle->setClickedCallback([=] { _delegate->playbackControlsVolumeToggled(); _volumeController->setValue(_delegate->playbackControlsCurrentVolume()); updateVolumeToggleIcon(); }); _speedController->setPseudoDiscrete( 7, [=](int index) { return (index + 2) / 4.; }, _delegate->playbackControlsCurrentSpeed(), [=](float64 speed) { updatePlaybackSpeed(speed); }); updatePlaybackSpeed(_delegate->playbackControlsCurrentSpeed()); _speedController->setAlwaysDisplayMarker(false); _playPauseResume->addClickHandler([=] { if (_showPause) { _delegate->playbackControlsPause(); } else { _delegate->playbackControlsPlay(); } }); _fullScreenToggle->addClickHandler([=] { if (_inFullScreen) { _delegate->playbackControlsFromFullScreen(); } else { _delegate->playbackControlsToFullScreen(); } }); _playbackProgress->setValueChangedCallback([=]( float64 value, float64 receivedTill) { _playbackSlider->setValue(value, receivedTill); }); _playbackSlider->setChangeProgressCallback([=](float64 value) { _playbackProgress->setValue(value, false); // This may destroy PlaybackControls. handleSeekProgress(value); }); _playbackSlider->setChangeFinishedCallback([=](float64 value) { _playbackProgress->setValue(value, false); handleSeekFinished(value); }); } void PlaybackControls::handleSeekProgress(float64 progress) { if (!_lastDurationMs) return; const auto positionMs = snap( static_cast(progress * _lastDurationMs), crl::time(0), _lastDurationMs); if (_seekPositionMs != positionMs) { _seekPositionMs = positionMs; refreshTimeTexts(); // This may destroy PlaybackControls. _delegate->playbackControlsSeekProgress(positionMs); } } void PlaybackControls::handleSeekFinished(float64 progress) { if (!_lastDurationMs) return; const auto positionMs = snap( static_cast(progress * _lastDurationMs), crl::time(0), _lastDurationMs); _seekPositionMs = -1; _delegate->playbackControlsSeekFinished(positionMs); refreshTimeTexts(); } template void PlaybackControls::startFading(Callback start) { if (!_fadeAnimation->animating()) { showChildren(); _playbackSlider->disablePaint(true); _volumeController->disablePaint(true); _speedController->disablePaint(true); _childrenHidden = false; } start(); if (_fadeAnimation->animating()) { for (const auto child : children()) { if (child->isWidgetType() && child != _playbackSlider && child != _volumeController && child != _speedController) { static_cast(child)->hide(); } } _childrenHidden = true; } else { fadeFinished(); } _playbackSlider->disablePaint(false); _volumeController->disablePaint(false); _speedController->disablePaint(false); } void PlaybackControls::showAnimated() { startFading([this]() { _fadeAnimation->fadeIn(st::mediaviewShowDuration); }); } void PlaybackControls::hideAnimated() { startFading([this]() { _fadeAnimation->fadeOut(st::mediaviewHideDuration); }); } void PlaybackControls::fadeFinished() { fadeUpdated(_fadeAnimation->visible() ? 1. : 0.); } void PlaybackControls::fadeUpdated(float64 opacity) { _playbackSlider->setFadeOpacity(opacity); _volumeController->setFadeOpacity(opacity); _speedController->setFadeOpacity(opacity); } void PlaybackControls::updatePlaybackSpeed(float64 speed) { _delegate->playbackControlsSpeedChanged(speed); //const auto percent = int(std::round(speed * 100.)); //_speedLabel->setText(QString::number(percent) + '%'); _speedLabel->setText(QString::number(speed) + 'x'); resizeEvent(nullptr); } void PlaybackControls::updatePlayback(const Player::TrackState &state) { updatePlayPauseResumeState(state); _playbackProgress->updateState(state, countDownloadedTillPercent(state)); updateTimeTexts(state); } void PlaybackControls::updateVolumeToggleIcon() { const auto volume = _delegate->playbackControlsCurrentVolume(); _volumeToggle->setIconOverride([&] { return (volume <= 0.) ? nullptr : (volume < 1 / 3.) ? &st::mediaviewVolumeIcon1 : (volume < 2 / 3.) ? &st::mediaviewVolumeIcon2 : &st::mediaviewVolumeIcon3; }(), [&] { return (volume <= 0.) ? nullptr : (volume < 1 / 3.) ? &st::mediaviewVolumeIcon1Over : (volume < 2 / 3.) ? &st::mediaviewVolumeIcon2Over : &st::mediaviewVolumeIcon3Over; }()); } float64 PlaybackControls::countDownloadedTillPercent( const Player::TrackState &state) const { if (_loadingReady > 0 && _loadingReady == _loadingTotal) { return 1.; } const auto header = state.fileHeaderSize; if (!header || _loadingReady <= header || _loadingTotal <= header) { return 0.; } return (_loadingReady - header) / float64(_loadingTotal - header); } void PlaybackControls::setLoadingProgress(int ready, int total) { if (_loadingReady == ready && _loadingTotal == total) { return; } _loadingReady = ready; _loadingTotal = total; if (_loadingReady != 0 && _loadingReady != _loadingTotal) { if (!_downloadProgress) { _downloadProgress.create(this, st::mediaviewPlayProgressLabel); _downloadProgress->setVisible(!_fadeAnimation->animating()); _loadingPercent = -1; } const auto progress = total ? (ready / float64(total)) : 0.; const auto percent = int(std::round(progress * 100)); if (_loadingPercent != percent) { _loadingPercent = percent; _downloadProgress->setText(tr::lng_mediaview_video_loading( tr::now, lt_percent, QString::number(percent) + '%')); if (_playbackSlider->width() > _downloadProgress->width()) { const auto left = (_playbackSlider->width() - _downloadProgress->width()) / 2; _downloadProgress->move(_playbackSlider->x() + left, st::mediaviewPlayProgressTop); } refreshFadeCache(); } } else { _downloadProgress.destroy(); } } void PlaybackControls::refreshFadeCache() { if (!_fadeAnimation->animating()) { return; } startFading([&] { _fadeAnimation->refreshCache(); }); } void PlaybackControls::updatePlayPauseResumeState(const Player::TrackState &state) { auto showPause = ShowPauseIcon(state.state) || (_seekPositionMs >= 0); if (showPause != _showPause) { _showPause = showPause; _playPauseResume->setIconOverride(_showPause ? &st::mediaviewPauseIcon : nullptr, _showPause ? &st::mediaviewPauseIconOver : nullptr); } } void PlaybackControls::updateTimeTexts(const Player::TrackState &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; } auto playFrequency = state.frequency; auto playAlready = position / playFrequency; auto playLeft = (state.length / playFrequency) - playAlready; _lastDurationMs = (state.length * crl::time(1000)) / playFrequency; _timeAlready = formatDurationText(playAlready); auto minus = QChar(8722); _timeLeft = minus + formatDurationText(playLeft); if (_seekPositionMs < 0) { refreshTimeTexts(); } } void PlaybackControls::refreshTimeTexts() { auto alreadyChanged = false, leftChanged = false; auto timeAlready = _timeAlready; auto timeLeft = _timeLeft; if (_seekPositionMs >= 0) { auto playAlready = _seekPositionMs / crl::time(1000); auto playLeft = (_lastDurationMs / crl::time(1000)) - playAlready; timeAlready = formatDurationText(playAlready); auto minus = QChar(8722); timeLeft = minus + formatDurationText(playLeft); } _playedAlready->setText(timeAlready, &alreadyChanged); _toPlayLeft->setText(timeLeft, &leftChanged); if (alreadyChanged || leftChanged) { resizeEvent(nullptr); refreshFadeCache(); } } void PlaybackControls::setInFullScreen(bool inFullScreen) { if (_inFullScreen != inFullScreen) { _inFullScreen = inFullScreen; _fullScreenToggle->setIconOverride( _inFullScreen ? &st::mediaviewFullScreenOutIcon : nullptr, _inFullScreen ? &st::mediaviewFullScreenOutIconOver : nullptr); } } void PlaybackControls::resizeEvent(QResizeEvent *e) { const auto skip = st::mediaviewFullScreenLeft; auto playbackWidth = width() - 2 * skip; _playbackSlider->resize(playbackWidth, st::mediaviewPlayback.seekSize.height()); _playbackSlider->moveToLeft(st::mediaviewPlayPauseLeft, st::mediaviewPlaybackTop); _playedAlready->moveToLeft(st::mediaviewPlayPauseLeft, st::mediaviewPlayProgressTop); _toPlayLeft->moveToRight(st::mediaviewPlayPauseLeft, st::mediaviewPlayProgressTop); if (_downloadProgress) { const auto left = (_playbackSlider->width() - _downloadProgress->width()) / 2; _downloadProgress->move(_playbackSlider->x() + left, st::mediaviewPlayProgressTop); } auto left = skip; const auto playTop = height() - _playPauseResume->height() - skip; _playPauseResume->moveToLeft(left, playTop); left += _playPauseResume->width() + skip; _fullScreenToggle->moveToLeft(left, playTop); left += _fullScreenToggle->width() + skip; _pictureInPicture->moveToLeft(left, playTop); auto right = skip; const auto volumeTop = playTop + (_fullScreenToggle->height() - _volumeController->height()) / 2; const auto volumeIconTop = playTop + (_fullScreenToggle->height() - _volumeToggle->height()) / 2; _volumeController->resize(st::mediaviewVolumeWidth, st::mediaviewPlayback.seekSize.height()); _volumeController->moveToRight(right, volumeTop); right += _volumeController->width(); _volumeToggle->moveToRight(right, volumeIconTop); right += _volumeToggle->width() + 2 * skip; const auto speedTop = volumeTop; const auto speedLabelTop = playTop + (_fullScreenToggle->height() - _speedLabel->height()) / 2; _speedController->resize(st::mediaviewVolumeWidth, st::mediaviewPlayback.seekSize.height()); _speedController->moveToRight(right, volumeTop); right += _speedController->width() + st::semiboldFont->spacew * 3; _speedLabel->moveToRight(right, speedLabelTop); } void PlaybackControls::paintEvent(QPaintEvent *e) { Painter p(this); if (_fadeAnimation->paint(p)) { return; } if (_childrenHidden) { showChildren(); _playbackSlider->setFadeOpacity(1.); _volumeController->setFadeOpacity(1.); _speedController->setFadeOpacity(1.); _childrenHidden = false; } App::roundRect(p, rect(), st::mediaviewSaveMsgBg, MediaviewSaveCorners); } void PlaybackControls::mousePressEvent(QMouseEvent *e) { e->accept(); // Don't pass event to the Media::View::OverlayWidget. } PlaybackControls::~PlaybackControls() = default; } // namespace View } // namespace Media