1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qmediaplaylistnavigator_p.h" |
41 | #include "qmediaplaylistprovider_p.h" |
42 | #include "qmediaplaylist.h" |
43 | #include "qmediaobject_p.h" |
44 | |
45 | #include <QtCore/qdebug.h> |
46 | #include <QtCore/qrandom.h> |
47 | |
48 | QT_BEGIN_NAMESPACE |
49 | |
50 | class QMediaPlaylistNullProvider : public QMediaPlaylistProvider |
51 | { |
52 | public: |
53 | QMediaPlaylistNullProvider() :QMediaPlaylistProvider() {} |
54 | ~QMediaPlaylistNullProvider() {} |
55 | int mediaCount() const override {return 0;} |
56 | QMediaContent media(int) const override { return QMediaContent(); } |
57 | }; |
58 | |
59 | Q_GLOBAL_STATIC(QMediaPlaylistNullProvider, _q_nullMediaPlaylist) |
60 | |
61 | class QMediaPlaylistNavigatorPrivate |
62 | { |
63 | Q_DECLARE_NON_CONST_PUBLIC(QMediaPlaylistNavigator) |
64 | public: |
65 | QMediaPlaylistNavigatorPrivate() |
66 | :playlist(nullptr), |
67 | currentPos(-1), |
68 | lastValidPos(-1), |
69 | playbackMode(QMediaPlaylist::Sequential), |
70 | randomPositionsOffset(-1) |
71 | { |
72 | } |
73 | |
74 | QMediaPlaylistProvider *playlist; |
75 | int currentPos; |
76 | int lastValidPos; //to be used with CurrentItemOnce playback mode |
77 | QMediaPlaylist::PlaybackMode playbackMode; |
78 | QMediaContent currentItem; |
79 | |
80 | mutable QList<int> randomModePositions; |
81 | mutable int randomPositionsOffset; |
82 | |
83 | int nextItemPos(int steps = 1) const; |
84 | int previousItemPos(int steps = 1) const; |
85 | |
86 | void _q_mediaInserted(int start, int end); |
87 | void _q_mediaRemoved(int start, int end); |
88 | void _q_mediaChanged(int start, int end); |
89 | |
90 | QMediaPlaylistNavigator *q_ptr; |
91 | }; |
92 | |
93 | |
94 | int QMediaPlaylistNavigatorPrivate::nextItemPos(int steps) const |
95 | { |
96 | if (playlist->mediaCount() == 0) |
97 | return -1; |
98 | |
99 | if (steps == 0) |
100 | return currentPos; |
101 | |
102 | switch (playbackMode) { |
103 | case QMediaPlaylist::CurrentItemOnce: |
104 | return /*currentPos == -1 ? lastValidPos :*/ -1; |
105 | case QMediaPlaylist::CurrentItemInLoop: |
106 | return currentPos; |
107 | case QMediaPlaylist::Sequential: |
108 | { |
109 | int nextPos = currentPos+steps; |
110 | return nextPos < playlist->mediaCount() ? nextPos : -1; |
111 | } |
112 | case QMediaPlaylist::Loop: |
113 | return (currentPos+steps) % playlist->mediaCount(); |
114 | case QMediaPlaylist::Random: |
115 | { |
116 | //TODO: limit the history size |
117 | |
118 | if (randomPositionsOffset == -1) { |
119 | randomModePositions.clear(); |
120 | randomModePositions.append(t: currentPos); |
121 | randomPositionsOffset = 0; |
122 | } |
123 | |
124 | while (randomModePositions.size() < randomPositionsOffset+steps+1) |
125 | randomModePositions.append(t: -1); |
126 | int res = randomModePositions[randomPositionsOffset+steps]; |
127 | if (res<0 || res >= playlist->mediaCount()) { |
128 | res = QRandomGenerator::global()->bounded(highest: playlist->mediaCount()); |
129 | randomModePositions[randomPositionsOffset+steps] = res; |
130 | } |
131 | |
132 | return res; |
133 | } |
134 | } |
135 | |
136 | return -1; |
137 | } |
138 | |
139 | int QMediaPlaylistNavigatorPrivate::previousItemPos(int steps) const |
140 | { |
141 | if (playlist->mediaCount() == 0) |
142 | return -1; |
143 | |
144 | if (steps == 0) |
145 | return currentPos; |
146 | |
147 | switch (playbackMode) { |
148 | case QMediaPlaylist::CurrentItemOnce: |
149 | return /*currentPos == -1 ? lastValidPos :*/ -1; |
150 | case QMediaPlaylist::CurrentItemInLoop: |
151 | return currentPos; |
152 | case QMediaPlaylist::Sequential: |
153 | { |
154 | int prevPos = currentPos == -1 ? playlist->mediaCount() - steps : currentPos - steps; |
155 | return prevPos>=0 ? prevPos : -1; |
156 | } |
157 | case QMediaPlaylist::Loop: |
158 | { |
159 | int prevPos = currentPos - steps; |
160 | while (prevPos<0) |
161 | prevPos += playlist->mediaCount(); |
162 | return prevPos; |
163 | } |
164 | case QMediaPlaylist::Random: |
165 | { |
166 | //TODO: limit the history size |
167 | |
168 | if (randomPositionsOffset == -1) { |
169 | randomModePositions.clear(); |
170 | randomModePositions.append(t: currentPos); |
171 | randomPositionsOffset = 0; |
172 | } |
173 | |
174 | while (randomPositionsOffset-steps < 0) { |
175 | randomModePositions.prepend(t: -1); |
176 | randomPositionsOffset++; |
177 | } |
178 | |
179 | int res = randomModePositions[randomPositionsOffset-steps]; |
180 | if (res<0 || res >= playlist->mediaCount()) { |
181 | res = QRandomGenerator::global()->bounded(highest: playlist->mediaCount()); |
182 | randomModePositions[randomPositionsOffset-steps] = res; |
183 | } |
184 | |
185 | return res; |
186 | } |
187 | } |
188 | |
189 | return -1; |
190 | } |
191 | |
192 | /*! |
193 | \class QMediaPlaylistNavigator |
194 | \internal |
195 | |
196 | \brief The QMediaPlaylistNavigator class provides navigation for a media playlist. |
197 | \inmodule QtMultimedia |
198 | \ingroup multimedia |
199 | \ingroup multimedia_playback |
200 | |
201 | \sa QMediaPlaylist, QMediaPlaylistProvider |
202 | */ |
203 | |
204 | |
205 | /*! |
206 | Constructs a media playlist navigator for a \a playlist. |
207 | |
208 | The \a parent is passed to QObject. |
209 | */ |
210 | QMediaPlaylistNavigator::QMediaPlaylistNavigator(QMediaPlaylistProvider *playlist, QObject *parent) |
211 | : QObject(parent) |
212 | , d_ptr(new QMediaPlaylistNavigatorPrivate) |
213 | { |
214 | d_ptr->q_ptr = this; |
215 | |
216 | setPlaylist(playlist ? playlist : _q_nullMediaPlaylist()); |
217 | } |
218 | |
219 | /*! |
220 | Destroys a media playlist navigator. |
221 | */ |
222 | |
223 | QMediaPlaylistNavigator::~QMediaPlaylistNavigator() |
224 | { |
225 | delete d_ptr; |
226 | } |
227 | |
228 | |
229 | /*! \property QMediaPlaylistNavigator::playbackMode |
230 | Contains the playback mode. |
231 | */ |
232 | QMediaPlaylist::PlaybackMode QMediaPlaylistNavigator::playbackMode() const |
233 | { |
234 | return d_func()->playbackMode; |
235 | } |
236 | |
237 | /*! |
238 | Sets the playback \a mode. |
239 | */ |
240 | void QMediaPlaylistNavigator::setPlaybackMode(QMediaPlaylist::PlaybackMode mode) |
241 | { |
242 | Q_D(QMediaPlaylistNavigator); |
243 | if (d->playbackMode == mode) |
244 | return; |
245 | |
246 | if (mode == QMediaPlaylist::Random) { |
247 | d->randomPositionsOffset = 0; |
248 | d->randomModePositions.append(t: d->currentPos); |
249 | } else if (d->playbackMode == QMediaPlaylist::Random) { |
250 | d->randomPositionsOffset = -1; |
251 | d->randomModePositions.clear(); |
252 | } |
253 | |
254 | d->playbackMode = mode; |
255 | |
256 | emit playbackModeChanged(mode); |
257 | emit surroundingItemsChanged(); |
258 | } |
259 | |
260 | /*! |
261 | Returns the playlist being navigated. |
262 | */ |
263 | |
264 | QMediaPlaylistProvider *QMediaPlaylistNavigator::playlist() const |
265 | { |
266 | return d_func()->playlist; |
267 | } |
268 | |
269 | /*! |
270 | Sets the \a playlist to navigate. |
271 | */ |
272 | void QMediaPlaylistNavigator::setPlaylist(QMediaPlaylistProvider *playlist) |
273 | { |
274 | Q_D(QMediaPlaylistNavigator); |
275 | |
276 | if (d->playlist == playlist) |
277 | return; |
278 | |
279 | if (d->playlist) { |
280 | d->playlist->disconnect(receiver: this); |
281 | } |
282 | |
283 | if (playlist) { |
284 | d->playlist = playlist; |
285 | } else { |
286 | //assign to shared readonly null playlist |
287 | d->playlist = _q_nullMediaPlaylist(); |
288 | } |
289 | |
290 | connect(asender: d->playlist, SIGNAL(mediaInserted(int,int)), SLOT(_q_mediaInserted(int,int))); |
291 | connect(asender: d->playlist, SIGNAL(mediaRemoved(int,int)), SLOT(_q_mediaRemoved(int,int))); |
292 | connect(asender: d->playlist, SIGNAL(mediaChanged(int,int)), SLOT(_q_mediaChanged(int,int))); |
293 | |
294 | d->randomPositionsOffset = -1; |
295 | d->randomModePositions.clear(); |
296 | |
297 | if (d->currentPos != -1) { |
298 | d->currentPos = -1; |
299 | emit currentIndexChanged(-1); |
300 | } |
301 | |
302 | if (!d->currentItem.isNull()) { |
303 | d->currentItem = QMediaContent(); |
304 | emit activated(content: d->currentItem); //stop playback |
305 | } |
306 | } |
307 | |
308 | /*! \property QMediaPlaylistNavigator::currentItem |
309 | |
310 | Contains the media at the current position in the playlist. |
311 | |
312 | \sa currentIndex() |
313 | */ |
314 | |
315 | QMediaContent QMediaPlaylistNavigator::currentItem() const |
316 | { |
317 | return itemAt(position: d_func()->currentPos); |
318 | } |
319 | |
320 | /*! \fn QMediaContent QMediaPlaylistNavigator::nextItem(int steps) const |
321 | |
322 | Returns the media that is \a steps positions ahead of the current |
323 | position in the playlist. |
324 | |
325 | \sa nextIndex() |
326 | */ |
327 | QMediaContent QMediaPlaylistNavigator::nextItem(int steps) const |
328 | { |
329 | return itemAt(position: nextIndex(steps)); |
330 | } |
331 | |
332 | /*! |
333 | Returns the media that is \a steps positions behind the current |
334 | position in the playlist. |
335 | |
336 | \sa previousIndex() |
337 | */ |
338 | QMediaContent QMediaPlaylistNavigator::previousItem(int steps) const |
339 | { |
340 | return itemAt(position: previousIndex(steps)); |
341 | } |
342 | |
343 | /*! |
344 | Returns the media at a \a position in the playlist. |
345 | */ |
346 | QMediaContent QMediaPlaylistNavigator::itemAt(int position) const |
347 | { |
348 | return d_func()->playlist->media(index: position); |
349 | } |
350 | |
351 | /*! \property QMediaPlaylistNavigator::currentIndex |
352 | |
353 | Contains the position of the current media. |
354 | |
355 | If no media is current, the property contains -1. |
356 | |
357 | \sa nextIndex(), previousIndex() |
358 | */ |
359 | |
360 | int QMediaPlaylistNavigator::currentIndex() const |
361 | { |
362 | return d_func()->currentPos; |
363 | } |
364 | |
365 | /*! |
366 | Returns a position \a steps ahead of the current position |
367 | accounting for the playbackMode(). |
368 | |
369 | If the position is beyond the end of the playlist, this value |
370 | returned is -1. |
371 | |
372 | \sa currentIndex(), previousIndex(), playbackMode() |
373 | */ |
374 | |
375 | int QMediaPlaylistNavigator::nextIndex(int steps) const |
376 | { |
377 | return d_func()->nextItemPos(steps); |
378 | } |
379 | |
380 | /*! |
381 | |
382 | Returns a position \a steps behind the current position accounting |
383 | for the playbackMode(). |
384 | |
385 | If the position is prior to the beginning of the playlist this will |
386 | return -1. |
387 | |
388 | \sa currentIndex(), nextIndex(), playbackMode() |
389 | */ |
390 | int QMediaPlaylistNavigator::previousIndex(int steps) const |
391 | { |
392 | return d_func()->previousItemPos(steps); |
393 | } |
394 | |
395 | /*! |
396 | Advances to the next item in the playlist. |
397 | |
398 | \sa previous(), jump(), playbackMode() |
399 | */ |
400 | void QMediaPlaylistNavigator::next() |
401 | { |
402 | Q_D(QMediaPlaylistNavigator); |
403 | |
404 | int nextPos = d->nextItemPos(); |
405 | |
406 | if ( playbackMode() == QMediaPlaylist::Random ) |
407 | d->randomPositionsOffset++; |
408 | |
409 | jump(nextPos); |
410 | } |
411 | |
412 | /*! |
413 | Returns to the previous item in the playlist, |
414 | |
415 | \sa next(), jump(), playbackMode() |
416 | */ |
417 | void QMediaPlaylistNavigator::previous() |
418 | { |
419 | Q_D(QMediaPlaylistNavigator); |
420 | |
421 | int prevPos = d->previousItemPos(); |
422 | if ( playbackMode() == QMediaPlaylist::Random ) |
423 | d->randomPositionsOffset--; |
424 | |
425 | jump(prevPos); |
426 | } |
427 | |
428 | /*! |
429 | Jumps to a new \a position in the playlist. |
430 | */ |
431 | void QMediaPlaylistNavigator::jump(int position) |
432 | { |
433 | Q_D(QMediaPlaylistNavigator); |
434 | |
435 | if (position < -1 || position >= d->playlist->mediaCount()) |
436 | position = -1; |
437 | |
438 | if (position != -1) |
439 | d->lastValidPos = position; |
440 | |
441 | if (playbackMode() == QMediaPlaylist::Random) { |
442 | if (d->randomModePositions[d->randomPositionsOffset] != position) { |
443 | d->randomModePositions.clear(); |
444 | d->randomModePositions.append(t: position); |
445 | d->randomPositionsOffset = 0; |
446 | } |
447 | } |
448 | |
449 | if (position != -1) |
450 | d->currentItem = d->playlist->media(index: position); |
451 | else |
452 | d->currentItem = QMediaContent(); |
453 | |
454 | if (position != d->currentPos) { |
455 | d->currentPos = position; |
456 | emit currentIndexChanged(d->currentPos); |
457 | emit surroundingItemsChanged(); |
458 | } |
459 | |
460 | emit activated(content: d->currentItem); |
461 | } |
462 | |
463 | /*! |
464 | \internal |
465 | */ |
466 | void QMediaPlaylistNavigatorPrivate::_q_mediaInserted(int start, int end) |
467 | { |
468 | Q_Q(QMediaPlaylistNavigator); |
469 | |
470 | if (currentPos >= start) { |
471 | currentPos = end-start+1; |
472 | q->jump(position: currentPos); |
473 | } |
474 | |
475 | //TODO: check if they really changed |
476 | emit q->surroundingItemsChanged(); |
477 | } |
478 | |
479 | /*! |
480 | \internal |
481 | */ |
482 | void QMediaPlaylistNavigatorPrivate::_q_mediaRemoved(int start, int end) |
483 | { |
484 | Q_Q(QMediaPlaylistNavigator); |
485 | |
486 | if (currentPos > end) { |
487 | currentPos = currentPos - end-start+1; |
488 | q->jump(position: currentPos); |
489 | } else if (currentPos >= start) { |
490 | //current item was removed |
491 | currentPos = qMin(a: start, b: playlist->mediaCount()-1); |
492 | q->jump(position: currentPos); |
493 | } |
494 | |
495 | //TODO: check if they really changed |
496 | emit q->surroundingItemsChanged(); |
497 | } |
498 | |
499 | /*! |
500 | \internal |
501 | */ |
502 | void QMediaPlaylistNavigatorPrivate::_q_mediaChanged(int start, int end) |
503 | { |
504 | Q_Q(QMediaPlaylistNavigator); |
505 | |
506 | if (currentPos >= start && currentPos<=end) { |
507 | QMediaContent src = playlist->media(index: currentPos); |
508 | if (src != currentItem) { |
509 | currentItem = src; |
510 | emit q->activated(content: src); |
511 | } |
512 | } |
513 | |
514 | //TODO: check if they really changed |
515 | emit q->surroundingItemsChanged(); |
516 | } |
517 | |
518 | /*! |
519 | \fn QMediaPlaylistNavigator::activated(const QMediaContent &media) |
520 | |
521 | Signals that the current \a media has changed. |
522 | */ |
523 | |
524 | /*! |
525 | \fn QMediaPlaylistNavigator::currentIndexChanged(int position) |
526 | |
527 | Signals the \a position of the current media has changed. |
528 | */ |
529 | |
530 | /*! |
531 | \fn QMediaPlaylistNavigator::playbackModeChanged(QMediaPlaylist::PlaybackMode mode) |
532 | |
533 | Signals that the playback \a mode has changed. |
534 | */ |
535 | |
536 | /*! |
537 | \fn QMediaPlaylistNavigator::surroundingItemsChanged() |
538 | |
539 | Signals that media immediately surrounding the current position has changed. |
540 | */ |
541 | |
542 | QT_END_NAMESPACE |
543 | |
544 | #include "moc_qmediaplaylistnavigator_p.cpp" |
545 | |