| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qmediaplaylistnavigator_p.h" |
| #include "qmediaplaylistprovider_p.h" |
| #include "qmediaplaylist.h" |
| #include "qmediaobject_p.h" |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qrandom.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| class QMediaPlaylistNullProvider : public QMediaPlaylistProvider |
| { |
| public: |
| QMediaPlaylistNullProvider() :QMediaPlaylistProvider() {} |
| ~QMediaPlaylistNullProvider() {} |
| int mediaCount() const override {return 0;} |
| QMediaContent media(int) const override { return QMediaContent(); } |
| }; |
| |
| Q_GLOBAL_STATIC(QMediaPlaylistNullProvider, _q_nullMediaPlaylist) |
| |
| class QMediaPlaylistNavigatorPrivate |
| { |
| Q_DECLARE_NON_CONST_PUBLIC(QMediaPlaylistNavigator) |
| public: |
| QMediaPlaylistNavigatorPrivate() |
| :playlist(nullptr), |
| currentPos(-1), |
| lastValidPos(-1), |
| playbackMode(QMediaPlaylist::Sequential), |
| randomPositionsOffset(-1) |
| { |
| } |
| |
| QMediaPlaylistProvider *playlist; |
| int currentPos; |
| int lastValidPos; //to be used with CurrentItemOnce playback mode |
| QMediaPlaylist::PlaybackMode playbackMode; |
| QMediaContent currentItem; |
| |
| mutable QList<int> randomModePositions; |
| mutable int randomPositionsOffset; |
| |
| int nextItemPos(int steps = 1) const; |
| int previousItemPos(int steps = 1) const; |
| |
| void _q_mediaInserted(int start, int end); |
| void _q_mediaRemoved(int start, int end); |
| void _q_mediaChanged(int start, int end); |
| |
| QMediaPlaylistNavigator *q_ptr; |
| }; |
| |
| |
| int QMediaPlaylistNavigatorPrivate::nextItemPos(int steps) const |
| { |
| if (playlist->mediaCount() == 0) |
| return -1; |
| |
| if (steps == 0) |
| return currentPos; |
| |
| switch (playbackMode) { |
| case QMediaPlaylist::CurrentItemOnce: |
| return /*currentPos == -1 ? lastValidPos :*/ -1; |
| case QMediaPlaylist::CurrentItemInLoop: |
| return currentPos; |
| case QMediaPlaylist::Sequential: |
| { |
| int nextPos = currentPos+steps; |
| return nextPos < playlist->mediaCount() ? nextPos : -1; |
| } |
| case QMediaPlaylist::Loop: |
| return (currentPos+steps) % playlist->mediaCount(); |
| case QMediaPlaylist::Random: |
| { |
| //TODO: limit the history size |
| |
| if (randomPositionsOffset == -1) { |
| randomModePositions.clear(); |
| randomModePositions.append(currentPos); |
| randomPositionsOffset = 0; |
| } |
| |
| while (randomModePositions.size() < randomPositionsOffset+steps+1) |
| randomModePositions.append(-1); |
| int res = randomModePositions[randomPositionsOffset+steps]; |
| if (res<0 || res >= playlist->mediaCount()) { |
| res = QRandomGenerator::global()->bounded(playlist->mediaCount()); |
| randomModePositions[randomPositionsOffset+steps] = res; |
| } |
| |
| return res; |
| } |
| } |
| |
| return -1; |
| } |
| |
| int QMediaPlaylistNavigatorPrivate::previousItemPos(int steps) const |
| { |
| if (playlist->mediaCount() == 0) |
| return -1; |
| |
| if (steps == 0) |
| return currentPos; |
| |
| switch (playbackMode) { |
| case QMediaPlaylist::CurrentItemOnce: |
| return /*currentPos == -1 ? lastValidPos :*/ -1; |
| case QMediaPlaylist::CurrentItemInLoop: |
| return currentPos; |
| case QMediaPlaylist::Sequential: |
| { |
| int prevPos = currentPos == -1 ? playlist->mediaCount() - steps : currentPos - steps; |
| return prevPos>=0 ? prevPos : -1; |
| } |
| case QMediaPlaylist::Loop: |
| { |
| int prevPos = currentPos - steps; |
| while (prevPos<0) |
| prevPos += playlist->mediaCount(); |
| return prevPos; |
| } |
| case QMediaPlaylist::Random: |
| { |
| //TODO: limit the history size |
| |
| if (randomPositionsOffset == -1) { |
| randomModePositions.clear(); |
| randomModePositions.append(currentPos); |
| randomPositionsOffset = 0; |
| } |
| |
| while (randomPositionsOffset-steps < 0) { |
| randomModePositions.prepend(-1); |
| randomPositionsOffset++; |
| } |
| |
| int res = randomModePositions[randomPositionsOffset-steps]; |
| if (res<0 || res >= playlist->mediaCount()) { |
| res = QRandomGenerator::global()->bounded(playlist->mediaCount()); |
| randomModePositions[randomPositionsOffset-steps] = res; |
| } |
| |
| return res; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /*! |
| \class QMediaPlaylistNavigator |
| \internal |
| |
| \brief The QMediaPlaylistNavigator class provides navigation for a media playlist. |
| \inmodule QtMultimedia |
| \ingroup multimedia |
| \ingroup multimedia_playback |
| |
| \sa QMediaPlaylist, QMediaPlaylistProvider |
| */ |
| |
| |
| /*! |
| Constructs a media playlist navigator for a \a playlist. |
| |
| The \a parent is passed to QObject. |
| */ |
| QMediaPlaylistNavigator::QMediaPlaylistNavigator(QMediaPlaylistProvider *playlist, QObject *parent) |
| : QObject(parent) |
| , d_ptr(new QMediaPlaylistNavigatorPrivate) |
| { |
| d_ptr->q_ptr = this; |
| |
| setPlaylist(playlist ? playlist : _q_nullMediaPlaylist()); |
| } |
| |
| /*! |
| Destroys a media playlist navigator. |
| */ |
| |
| QMediaPlaylistNavigator::~QMediaPlaylistNavigator() |
| { |
| delete d_ptr; |
| } |
| |
| |
| /*! \property QMediaPlaylistNavigator::playbackMode |
| Contains the playback mode. |
| */ |
| QMediaPlaylist::PlaybackMode QMediaPlaylistNavigator::playbackMode() const |
| { |
| return d_func()->playbackMode; |
| } |
| |
| /*! |
| Sets the playback \a mode. |
| */ |
| void QMediaPlaylistNavigator::setPlaybackMode(QMediaPlaylist::PlaybackMode mode) |
| { |
| Q_D(QMediaPlaylistNavigator); |
| if (d->playbackMode == mode) |
| return; |
| |
| if (mode == QMediaPlaylist::Random) { |
| d->randomPositionsOffset = 0; |
| d->randomModePositions.append(d->currentPos); |
| } else if (d->playbackMode == QMediaPlaylist::Random) { |
| d->randomPositionsOffset = -1; |
| d->randomModePositions.clear(); |
| } |
| |
| d->playbackMode = mode; |
| |
| emit playbackModeChanged(mode); |
| emit surroundingItemsChanged(); |
| } |
| |
| /*! |
| Returns the playlist being navigated. |
| */ |
| |
| QMediaPlaylistProvider *QMediaPlaylistNavigator::playlist() const |
| { |
| return d_func()->playlist; |
| } |
| |
| /*! |
| Sets the \a playlist to navigate. |
| */ |
| void QMediaPlaylistNavigator::setPlaylist(QMediaPlaylistProvider *playlist) |
| { |
| Q_D(QMediaPlaylistNavigator); |
| |
| if (d->playlist == playlist) |
| return; |
| |
| if (d->playlist) { |
| d->playlist->disconnect(this); |
| } |
| |
| if (playlist) { |
| d->playlist = playlist; |
| } else { |
| //assign to shared readonly null playlist |
| d->playlist = _q_nullMediaPlaylist(); |
| } |
| |
| connect(d->playlist, SIGNAL(mediaInserted(int,int)), SLOT(_q_mediaInserted(int,int))); |
| connect(d->playlist, SIGNAL(mediaRemoved(int,int)), SLOT(_q_mediaRemoved(int,int))); |
| connect(d->playlist, SIGNAL(mediaChanged(int,int)), SLOT(_q_mediaChanged(int,int))); |
| |
| d->randomPositionsOffset = -1; |
| d->randomModePositions.clear(); |
| |
| if (d->currentPos != -1) { |
| d->currentPos = -1; |
| emit currentIndexChanged(-1); |
| } |
| |
| if (!d->currentItem.isNull()) { |
| d->currentItem = QMediaContent(); |
| emit activated(d->currentItem); //stop playback |
| } |
| } |
| |
| /*! \property QMediaPlaylistNavigator::currentItem |
| |
| Contains the media at the current position in the playlist. |
| |
| \sa currentIndex() |
| */ |
| |
| QMediaContent QMediaPlaylistNavigator::currentItem() const |
| { |
| return itemAt(d_func()->currentPos); |
| } |
| |
| /*! \fn QMediaContent QMediaPlaylistNavigator::nextItem(int steps) const |
| |
| Returns the media that is \a steps positions ahead of the current |
| position in the playlist. |
| |
| \sa nextIndex() |
| */ |
| QMediaContent QMediaPlaylistNavigator::nextItem(int steps) const |
| { |
| return itemAt(nextIndex(steps)); |
| } |
| |
| /*! |
| Returns the media that is \a steps positions behind the current |
| position in the playlist. |
| |
| \sa previousIndex() |
| */ |
| QMediaContent QMediaPlaylistNavigator::previousItem(int steps) const |
| { |
| return itemAt(previousIndex(steps)); |
| } |
| |
| /*! |
| Returns the media at a \a position in the playlist. |
| */ |
| QMediaContent QMediaPlaylistNavigator::itemAt(int position) const |
| { |
| return d_func()->playlist->media(position); |
| } |
| |
| /*! \property QMediaPlaylistNavigator::currentIndex |
| |
| Contains the position of the current media. |
| |
| If no media is current, the property contains -1. |
| |
| \sa nextIndex(), previousIndex() |
| */ |
| |
| int QMediaPlaylistNavigator::currentIndex() const |
| { |
| return d_func()->currentPos; |
| } |
| |
| /*! |
| Returns a position \a steps ahead of the current position |
| accounting for the playbackMode(). |
| |
| If the position is beyond the end of the playlist, this value |
| returned is -1. |
| |
| \sa currentIndex(), previousIndex(), playbackMode() |
| */ |
| |
| int QMediaPlaylistNavigator::nextIndex(int steps) const |
| { |
| return d_func()->nextItemPos(steps); |
| } |
| |
| /*! |
| |
| Returns a position \a steps behind the current position accounting |
| for the playbackMode(). |
| |
| If the position is prior to the beginning of the playlist this will |
| return -1. |
| |
| \sa currentIndex(), nextIndex(), playbackMode() |
| */ |
| int QMediaPlaylistNavigator::previousIndex(int steps) const |
| { |
| return d_func()->previousItemPos(steps); |
| } |
| |
| /*! |
| Advances to the next item in the playlist. |
| |
| \sa previous(), jump(), playbackMode() |
| */ |
| void QMediaPlaylistNavigator::next() |
| { |
| Q_D(QMediaPlaylistNavigator); |
| |
| int nextPos = d->nextItemPos(); |
| |
| if ( playbackMode() == QMediaPlaylist::Random ) |
| d->randomPositionsOffset++; |
| |
| jump(nextPos); |
| } |
| |
| /*! |
| Returns to the previous item in the playlist, |
| |
| \sa next(), jump(), playbackMode() |
| */ |
| void QMediaPlaylistNavigator::previous() |
| { |
| Q_D(QMediaPlaylistNavigator); |
| |
| int prevPos = d->previousItemPos(); |
| if ( playbackMode() == QMediaPlaylist::Random ) |
| d->randomPositionsOffset--; |
| |
| jump(prevPos); |
| } |
| |
| /*! |
| Jumps to a new \a position in the playlist. |
| */ |
| void QMediaPlaylistNavigator::jump(int position) |
| { |
| Q_D(QMediaPlaylistNavigator); |
| |
| if (position < -1 || position >= d->playlist->mediaCount()) |
| position = -1; |
| |
| if (position != -1) |
| d->lastValidPos = position; |
| |
| if (playbackMode() == QMediaPlaylist::Random) { |
| if (d->randomModePositions[d->randomPositionsOffset] != position) { |
| d->randomModePositions.clear(); |
| d->randomModePositions.append(position); |
| d->randomPositionsOffset = 0; |
| } |
| } |
| |
| if (position != -1) |
| d->currentItem = d->playlist->media(position); |
| else |
| d->currentItem = QMediaContent(); |
| |
| if (position != d->currentPos) { |
| d->currentPos = position; |
| emit currentIndexChanged(d->currentPos); |
| emit surroundingItemsChanged(); |
| } |
| |
| emit activated(d->currentItem); |
| } |
| |
| /*! |
| \internal |
| */ |
| void QMediaPlaylistNavigatorPrivate::_q_mediaInserted(int start, int end) |
| { |
| Q_Q(QMediaPlaylistNavigator); |
| |
| if (currentPos >= start) { |
| currentPos = end-start+1; |
| q->jump(currentPos); |
| } |
| |
| //TODO: check if they really changed |
| emit q->surroundingItemsChanged(); |
| } |
| |
| /*! |
| \internal |
| */ |
| void QMediaPlaylistNavigatorPrivate::_q_mediaRemoved(int start, int end) |
| { |
| Q_Q(QMediaPlaylistNavigator); |
| |
| if (currentPos > end) { |
| currentPos = currentPos - end-start+1; |
| q->jump(currentPos); |
| } else if (currentPos >= start) { |
| //current item was removed |
| currentPos = qMin(start, playlist->mediaCount()-1); |
| q->jump(currentPos); |
| } |
| |
| //TODO: check if they really changed |
| emit q->surroundingItemsChanged(); |
| } |
| |
| /*! |
| \internal |
| */ |
| void QMediaPlaylistNavigatorPrivate::_q_mediaChanged(int start, int end) |
| { |
| Q_Q(QMediaPlaylistNavigator); |
| |
| if (currentPos >= start && currentPos<=end) { |
| QMediaContent src = playlist->media(currentPos); |
| if (src != currentItem) { |
| currentItem = src; |
| emit q->activated(src); |
| } |
| } |
| |
| //TODO: check if they really changed |
| emit q->surroundingItemsChanged(); |
| } |
| |
| /*! |
| \fn QMediaPlaylistNavigator::activated(const QMediaContent &media) |
| |
| Signals that the current \a media has changed. |
| */ |
| |
| /*! |
| \fn QMediaPlaylistNavigator::currentIndexChanged(int position) |
| |
| Signals the \a position of the current media has changed. |
| */ |
| |
| /*! |
| \fn QMediaPlaylistNavigator::playbackModeChanged(QMediaPlaylist::PlaybackMode mode) |
| |
| Signals that the playback \a mode has changed. |
| */ |
| |
| /*! |
| \fn QMediaPlaylistNavigator::surroundingItemsChanged() |
| |
| Signals that media immediately surrounding the current position has changed. |
| */ |
| |
| QT_END_NAMESPACE |
| |
| #include "moc_qmediaplaylistnavigator_p.cpp" |