]> cloud.milkyroute.net Git - dolphin.git/blob - src/panels/information/mediawidget.cpp
mediawidget: bind arrow keys to slide in media
[dolphin.git] / src / panels / information / mediawidget.cpp
1 /*
2 SPDX-FileCopyrightText: 2007 Matthias Kretz <kretz@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "mediawidget.h"
8
9 #include <KLocalizedString>
10
11 #include <QAudioOutput>
12 #include <QMediaPlayer>
13 #include <QVideoWidget>
14
15 #include <QShowEvent>
16 #include <QSlider>
17 #include <QStyle>
18 #include <QStyleOptionSlider>
19 #include <QToolButton>
20 #include <QVBoxLayout>
21 #include <qnamespace.h>
22 #include <qwidget.h>
23
24 class EmbeddedVideoPlayer : public QVideoWidget
25 {
26 Q_OBJECT
27
28 public:
29 EmbeddedVideoPlayer(QWidget *parent = nullptr)
30 : QVideoWidget(parent)
31 {
32 }
33
34 void setSizeHint(const QSize &size)
35 {
36 m_sizeHint = size;
37 updateGeometry();
38 }
39
40 QSize sizeHint() const override
41 {
42 return m_sizeHint.isValid() ? m_sizeHint : QVideoWidget::sizeHint();
43 }
44
45 private:
46 QSize m_sizeHint;
47 };
48
49 class SeekSlider : public QSlider
50 {
51 Q_OBJECT
52
53 public:
54 SeekSlider(Qt::Orientation orientation, QWidget *parent = nullptr)
55 : QSlider(orientation, parent)
56 {
57 }
58
59 protected:
60 // Function copied from qslider.cpp
61 inline int pick(const QPoint &pt) const
62 {
63 return orientation() == Qt::Horizontal ? pt.x() : pt.y();
64 }
65
66 // Function copied from qslider.cpp and modified to make it compile
67 int pixelPosToRangeValue(int pos) const
68 {
69 QStyleOptionSlider opt;
70 initStyleOption(&opt);
71 QRect gr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this);
72 QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
73 int sliderMin, sliderMax, sliderLength;
74
75 if (orientation() == Qt::Horizontal) {
76 sliderLength = sr.width();
77 sliderMin = gr.x();
78 sliderMax = gr.right() - sliderLength + 1;
79 } else {
80 sliderLength = sr.height();
81 sliderMin = gr.y();
82 sliderMax = gr.bottom() - sliderLength + 1;
83 }
84 return QStyle::sliderValueFromPosition(minimum(), maximum(), pos - sliderMin, sliderMax - sliderMin, opt.upsideDown);
85 }
86
87 // Based on code from qslider.cpp
88 void mousePressEvent(QMouseEvent *event) override
89 {
90 if (event->button() == Qt::LeftButton) {
91 QStyleOptionSlider opt;
92 initStyleOption(&opt);
93 const QRect sliderRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
94 const QPoint center = sliderRect.center() - sliderRect.topLeft();
95 // to take half of the slider off for the setSliderPosition call we use the center - topLeft
96
97 if (!sliderRect.contains(event->pos())) {
98 event->accept();
99
100 int position = pixelPosToRangeValue(pick(event->pos() - center));
101 setSliderPosition(position);
102 triggerAction(SliderMove);
103 setRepeatAction(SliderNoAction);
104
105 Q_EMIT sliderMoved(position);
106 } else {
107 QSlider::mousePressEvent(event);
108 }
109 } else {
110 QSlider::mousePressEvent(event);
111 }
112 }
113
114 void keyPressEvent(QKeyEvent *event) override
115 {
116 int newPosition = -1;
117 if (event->key() == Qt::Key_Right) {
118 // slide right 1%
119 newPosition = std::min(maximum(), sliderPosition() + maximum() / 100);
120 } else if (event->key() == Qt::Key_Left) {
121 // slide left 1%
122 newPosition = std::max(0, sliderPosition() - maximum() / 100);
123 }
124
125 if (newPosition != -1) {
126 event->accept();
127
128 if (newPosition != sliderPosition()) {
129 setSliderPosition(newPosition);
130 triggerAction(SliderMove);
131 setRepeatAction(SliderNoAction);
132
133 Q_EMIT sliderMoved(newPosition);
134 }
135 } else {
136 QSlider::keyPressEvent(event);
137 }
138 }
139 };
140
141 MediaWidget::MediaWidget(QWidget *parent)
142 : QWidget(parent)
143 , m_url()
144 , m_playButton(nullptr)
145 , m_pauseButton(nullptr)
146 , m_topLayout(nullptr)
147 , m_player(nullptr)
148 , m_seekSlider(nullptr)
149 , m_videoWidget(nullptr)
150 {
151 }
152
153 void MediaWidget::setUrl(const QUrl &url, MediaKind kind)
154 {
155 if (m_url != url) {
156 m_url = url;
157 m_isVideo = kind == MediaKind::Video;
158 m_seekSlider->setValue(0);
159 }
160 if (m_autoPlay) {
161 play();
162 } else {
163 stop();
164 }
165 }
166
167 void MediaWidget::setAutoPlay(bool autoPlay)
168 {
169 m_autoPlay = autoPlay;
170 if (!m_url.isEmpty() && (m_player == nullptr || m_player->playbackState() != QMediaPlayer::PlayingState) && m_autoPlay && isVisible()) {
171 play();
172 }
173 }
174
175 QUrl MediaWidget::url() const
176 {
177 return m_url;
178 }
179
180 void MediaWidget::clearUrl()
181 {
182 m_url.clear();
183 }
184
185 void MediaWidget::togglePlayback()
186 {
187 if (m_player && m_player->playbackState() == QMediaPlayer::PlayingState) {
188 m_player->pause();
189 } else {
190 play();
191 }
192 }
193
194 bool MediaWidget::eventFilter(QObject *object, QEvent *event)
195 {
196 Q_UNUSED(object)
197 if (event->type() == QEvent::MouseButtonPress) {
198 const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
199 if (mouseEvent->button() == Qt::LeftButton) {
200 // toggle playback
201 togglePlayback();
202 return true;
203 }
204 }
205 return false;
206 }
207
208 void MediaWidget::setVideoSize(const QSize &size)
209 {
210 if (m_videoSize != size) {
211 m_videoSize = size;
212 applyVideoSize();
213 }
214 }
215
216 QSize MediaWidget::videoSize() const
217 {
218 return m_videoSize;
219 }
220
221 void MediaWidget::showEvent(QShowEvent *event)
222 {
223 if (event->spontaneous()) {
224 QWidget::showEvent(event);
225 return;
226 }
227
228 if (!m_topLayout) {
229 m_topLayout = new QVBoxLayout(this);
230 m_topLayout->setContentsMargins(0, 0, 0, 0);
231
232 QHBoxLayout *controlsLayout = new QHBoxLayout();
233 controlsLayout->setContentsMargins(0, 0, 0, 0);
234 controlsLayout->setSpacing(0);
235
236 m_playButton = new QToolButton(this);
237 m_pauseButton = new QToolButton(this);
238 m_seekSlider = new SeekSlider(Qt::Orientation::Horizontal, this);
239 connect(m_seekSlider, &QAbstractSlider::sliderMoved, this, &MediaWidget::setPosition);
240
241 controlsLayout->addWidget(m_playButton);
242 controlsLayout->addWidget(m_pauseButton);
243 controlsLayout->addWidget(m_seekSlider);
244
245 m_topLayout->addLayout(controlsLayout);
246
247 const int smallIconSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
248 const QSize buttonSize(smallIconSize, smallIconSize);
249
250 m_playButton->setToolTip(i18n("play"));
251 m_playButton->setIconSize(buttonSize);
252 m_playButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
253 m_playButton->setAutoRaise(true);
254 connect(m_playButton, &QToolButton::clicked, this, &MediaWidget::play);
255
256 m_pauseButton->setToolTip(i18n("pause"));
257 m_pauseButton->setIconSize(buttonSize);
258 m_pauseButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")));
259 m_pauseButton->setAutoRaise(true);
260 m_pauseButton->hide();
261 connect(m_pauseButton, &QToolButton::clicked, this, &MediaWidget::togglePlayback);
262 }
263 }
264
265 void MediaWidget::hideEvent(QHideEvent *event)
266 {
267 QWidget::hideEvent(event);
268 if (!event->spontaneous()) {
269 stop();
270 }
271 }
272
273 void MediaWidget::onStateChanged(QMediaPlayer::PlaybackState newState)
274 {
275 setUpdatesEnabled(false);
276 switch (newState) {
277 case QMediaPlayer::PlaybackState::PlayingState:
278 m_playButton->hide();
279 m_pauseButton->show();
280 break;
281 default:
282 m_pauseButton->hide();
283 m_playButton->show();
284 break;
285 }
286 setUpdatesEnabled(true);
287 }
288
289 void MediaWidget::initPlayer()
290 {
291 if (!m_player) {
292 m_player = new QMediaPlayer;
293 m_player->setAudioOutput(new QAudioOutput);
294
295 m_videoWidget = new EmbeddedVideoPlayer(this);
296 m_videoWidget->setCursor(Qt::PointingHandCursor);
297
298 m_videoWidget->installEventFilter(this);
299 m_player->setVideoOutput(m_videoWidget);
300 m_topLayout->insertWidget(0, m_videoWidget);
301
302 applyVideoSize();
303
304 connect(m_player, &QMediaPlayer::playbackStateChanged, this, &MediaWidget::onStateChanged);
305 connect(m_player, &QMediaPlayer::positionChanged, this, &MediaWidget::onPositionChanged);
306 connect(m_player, &QMediaPlayer::durationChanged, this, &MediaWidget::onDurationChanged);
307 }
308
309 if (m_url != m_player->source()) {
310 m_player->setSource(m_url);
311 m_seekSlider->setSliderPosition(0);
312 }
313
314 Q_EMIT hasVideoChanged(m_isVideo);
315
316 m_videoWidget->setVisible(m_isVideo);
317 }
318
319 void MediaWidget::play()
320 {
321 initPlayer();
322
323 m_player->play();
324 }
325
326 void MediaWidget::finished()
327 {
328 if (m_isVideo) {
329 m_videoWidget->hide();
330 Q_EMIT hasVideoChanged(false);
331 }
332 }
333
334 QMediaPlayer::PlaybackState MediaWidget::state() const
335 {
336 return m_player == nullptr ? QMediaPlayer::PlaybackState::StoppedState : m_player->playbackState();
337 }
338
339 void MediaWidget::stop()
340 {
341 if (m_player) {
342 m_player->stop();
343 m_videoWidget->hide();
344 Q_EMIT hasVideoChanged(false);
345 }
346 }
347
348 void MediaWidget::applyVideoSize()
349 {
350 if ((m_videoWidget) && m_videoSize.isValid()) {
351 m_videoWidget->setSizeHint(m_videoSize);
352 }
353 }
354
355 void MediaWidget::setPosition(qint64 position)
356 {
357 if (!m_player || m_player->playbackState() == QMediaPlayer::StoppedState) {
358 initPlayer();
359
360 auto prevDuration = m_seekSlider->maximum();
361
362 connect(
363 m_player,
364 &QMediaPlayer::mediaStatusChanged,
365 this,
366 [prevDuration, position, this](QMediaPlayer::MediaStatus status) {
367 if (status == QMediaPlayer::BufferedMedia) {
368 m_player->setPosition(float(position) / prevDuration * m_player->duration());
369 m_player->pause();
370 }
371 },
372 Qt::SingleShotConnection);
373
374 m_player->play();
375 } else {
376 m_player->setPosition(position);
377 }
378 }
379
380 void MediaWidget::onPositionChanged(qint64 position)
381 {
382 m_seekSlider->setValue(static_cast<int>(position));
383 }
384
385 void MediaWidget::onDurationChanged(qint64 duration)
386 {
387 m_seekSlider->setMaximum(static_cast<int>(duration));
388 }
389
390 #include "mediawidget.moc"
391 #include "moc_mediawidget.cpp"