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