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
48QT_BEGIN_NAMESPACE
49
50class QMediaPlaylistNullProvider : public QMediaPlaylistProvider
51{
52public:
53 QMediaPlaylistNullProvider() :QMediaPlaylistProvider() {}
54 ~QMediaPlaylistNullProvider() {}
55 int mediaCount() const override {return 0;}
56 QMediaContent media(int) const override { return QMediaContent(); }
57};
58
59Q_GLOBAL_STATIC(QMediaPlaylistNullProvider, _q_nullMediaPlaylist)
60
61class QMediaPlaylistNavigatorPrivate
62{
63 Q_DECLARE_NON_CONST_PUBLIC(QMediaPlaylistNavigator)
64public:
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
94int 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
139int 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 */
210QMediaPlaylistNavigator::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
223QMediaPlaylistNavigator::~QMediaPlaylistNavigator()
224{
225 delete d_ptr;
226}
227
228
229/*! \property QMediaPlaylistNavigator::playbackMode
230 Contains the playback mode.
231 */
232QMediaPlaylist::PlaybackMode QMediaPlaylistNavigator::playbackMode() const
233{
234 return d_func()->playbackMode;
235}
236
237/*!
238 Sets the playback \a mode.
239 */
240void 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
264QMediaPlaylistProvider *QMediaPlaylistNavigator::playlist() const
265{
266 return d_func()->playlist;
267}
268
269/*!
270 Sets the \a playlist to navigate.
271*/
272void 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
315QMediaContent 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*/
327QMediaContent 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 */
338QMediaContent 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 */
346QMediaContent 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
360int 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
375int 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*/
390int 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 */
400void 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 */
417void 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 */
431void 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*/
466void 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*/
482void 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*/
502void 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
542QT_END_NAMESPACE
543
544#include "moc_qmediaplaylistnavigator_p.cpp"
545

source code of qtmultimedia/src/multimedia/playback/qmediaplaylistnavigator.cpp