/****************************************************************************
**
** 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 "qmediaplayer.h"
#include "qvideosurfaceoutput_p.h"

#include "qmediaobject_p.h"
#include <qmediaservice.h>
#include <qmediaplayercontrol.h>
#include <qmediaserviceprovider_p.h>
#include <qmediaplaylist.h>
#include <qmediaplaylistcontrol_p.h>
#include <qmediaplaylistsourcecontrol_p.h>
#include <qmedianetworkaccesscontrol.h>
#include <qaudiorolecontrol.h>
#include <qcustomaudiorolecontrol.h>

#include <QtCore/qcoreevent.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qtimer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qpointer.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qtemporaryfile.h>

QT_BEGIN_NAMESPACE

/*!
    \class QMediaPlayer
    \brief The QMediaPlayer class allows the playing of a media source.
    \inmodule QtMultimedia
    \ingroup multimedia
    \ingroup multimedia_playback

    The QMediaPlayer class is a high level media playback class. It can be used
    to playback such content as songs, movies and internet radio. The content
    to playback is specified as a QMediaContent object, which can be thought of as a
    main or canonical URL with additional information attached. When provided
    with a QMediaContent playback may be able to commence.

    \snippet multimedia-snippets/media.cpp Player

    QVideoWidget can be used with QMediaPlayer for video rendering and QMediaPlaylist
    for accessing playlist functionality.

    \snippet multimedia-snippets/media.cpp Movie playlist

    Since QMediaPlayer is a QMediaObject, you can use several of the QMediaObject
    functions for things like:

    \list
    \li Accessing the currently playing media's metadata (\l {QMediaObject::metaData()} and \l {QMediaMetaData}{predefined meta-data keys})
    \li Checking to see if the media playback service is currently available (\l {QMediaObject::availability()})
    \endlist

    \sa QMediaObject, QMediaService, QVideoWidget, QMediaPlaylist
*/

static void qRegisterMediaPlayerMetaTypes()
{
    qRegisterMetaType<QMediaPlayer::State>("QMediaPlayer::State");
    qRegisterMetaType<QMediaPlayer::MediaStatus>("QMediaPlayer::MediaStatus");
    qRegisterMetaType<QMediaPlayer::Error>("QMediaPlayer::Error");
}

Q_CONSTRUCTOR_FUNCTION(qRegisterMediaPlayerMetaTypes)

#define MAX_NESTED_PLAYLISTS 16

class QMediaPlayerPrivate : public QMediaObjectPrivate
{
    Q_DECLARE_NON_CONST_PUBLIC(QMediaPlayer)

public:
    QMediaPlayerPrivate()
        : provider(nullptr)
        , control(nullptr)
        , audioRoleControl(nullptr)
        , customAudioRoleControl(nullptr)
        , playlist(nullptr)
        , networkAccessControl(nullptr)
        , state(QMediaPlayer::StoppedState)
        , status(QMediaPlayer::UnknownMediaStatus)
        , error(QMediaPlayer::NoError)
        , ignoreNextStatusChange(-1)
        , nestedPlaylists(0)
        , hasStreamPlaybackFeature(false)
    {}

    QMediaServiceProvider *provider;
    QMediaPlayerControl* control;
    QAudioRoleControl *audioRoleControl;
    QCustomAudioRoleControl *customAudioRoleControl;
    QString errorString;

    QPointer<QObject> videoOutput;
    QMediaPlaylist *playlist;
    QMediaNetworkAccessControl *networkAccessControl;
    QVideoSurfaceOutput surfaceOutput;
    QMediaContent qrcMedia;
    QScopedPointer<QFile> qrcFile;

    QMediaContent rootMedia;
    QMediaContent pendingPlaylist;
    QMediaPlayer::State state;
    QMediaPlayer::MediaStatus status;
    QMediaPlayer::Error error;
    int ignoreNextStatusChange;
    int nestedPlaylists;
    bool hasStreamPlaybackFeature;

    QMediaPlaylist *parentPlaylist(QMediaPlaylist *pls);
    bool isInChain(const QUrl &url);

    void setMedia(const QMediaContent &media, QIODevice *stream = nullptr);

    void setPlaylist(QMediaPlaylist *playlist);
    void setPlaylistMedia();
    void loadPlaylist();
    void disconnectPlaylist();
    void connectPlaylist();

    void _q_stateChanged(QMediaPlayer::State state);
    void _q_mediaStatusChanged(QMediaPlayer::MediaStatus status);
    void _q_error(int error, const QString &errorString);
    void _q_updateMedia(const QMediaContent&);
    void _q_playlistDestroyed();
    void _q_handleMediaChanged(const QMediaContent&);
    void _q_handlePlaylistLoaded();
    void _q_handlePlaylistLoadFailed();
};

QMediaPlaylist *QMediaPlayerPrivate::parentPlaylist(QMediaPlaylist *pls)
{
    // This function finds a parent playlist for an item in the active chain of playlists.
    // Every item in the chain comes from currentMedia() of its parent.
    // We don't need to travers the whole tree of playlists,
    // but only the subtree of active ones.
    for (QMediaPlaylist *current = rootMedia.playlist(); current && current != pls; current = current->currentMedia().playlist())
        if (current->currentMedia().playlist() == pls)
            return current;
    return nullptr;
}

bool QMediaPlayerPrivate::isInChain(const QUrl &url)
{
    // Check whether a URL is already in the chain of playlists.
    // Also see a comment in parentPlaylist().
    for (QMediaPlaylist *current = rootMedia.playlist(); current && current != playlist; current = current->currentMedia().playlist())
        if (current->currentMedia().canonicalUrl() == url) {
            return true;
        }
    return false;
}

void QMediaPlayerPrivate::_q_stateChanged(QMediaPlayer::State ps)
{
    Q_Q(QMediaPlayer);

    // Backend switches into stopped state every time new media is about to be loaded.
    // If media player has a playlist loaded make sure player doesn' stop.
    if (playlist && playlist->currentIndex() != -1 && ps != state && ps == QMediaPlayer::StoppedState) {
        if (control->mediaStatus() == QMediaPlayer::EndOfMedia ||
                control->mediaStatus() == QMediaPlayer::InvalidMedia) {
            // if media player is not stopped, and
            // we have finished playback for the current media,
            // advance to the next item in the playlist
            Q_ASSERT(state != QMediaPlayer::StoppedState);
            playlist->next();
            return;
        } else if (control->mediaStatus() == QMediaPlayer::LoadingMedia) {
            return;
        }
    }

    if (ps != state) {
        state = ps;

        if (ps == QMediaPlayer::PlayingState)
            q->addPropertyWatch("position");
        else
            q->removePropertyWatch("position");

        emit q->stateChanged(ps);
    }
}

void QMediaPlayerPrivate::_q_mediaStatusChanged(QMediaPlayer::MediaStatus s)
{
    Q_Q(QMediaPlayer);

    if (int(s) == ignoreNextStatusChange) {
        ignoreNextStatusChange = -1;
        return;
    }

    if (s != status) {
        status = s;

        switch (s) {
        case QMediaPlayer::StalledMedia:
        case QMediaPlayer::BufferingMedia:
            q->addPropertyWatch("bufferStatus");
            break;
        default:
            q->removePropertyWatch("bufferStatus");
            break;
        }

        emit q->mediaStatusChanged(s);
    }
}

void QMediaPlayerPrivate::_q_error(int error, const QString &errorString)
{
    Q_Q(QMediaPlayer);

    if (error == int(QMediaPlayer::MediaIsPlaylist)) {
        loadPlaylist();
    } else {
        this->error = QMediaPlayer::Error(error);
        this->errorString = errorString;
        emit q->error(this->error);

        if (playlist)
            playlist->next();
    }
}

void QMediaPlayerPrivate::_q_updateMedia(const QMediaContent &media)
{
    Q_Q(QMediaPlayer);

    if (!control)
        return;

    // check if the current playlist is a top-level playlist
    Q_ASSERT(playlist);
    if (media.isNull() && playlist != rootMedia.playlist()) {
        // switch back to parent playlist
        QMediaPlaylist *pls = parentPlaylist(playlist);
        Q_ASSERT(pls);
        disconnectPlaylist();
        playlist = pls;
        connectPlaylist();

        Q_ASSERT(!pendingPlaylist.playlist());
        nestedPlaylists--;
        Q_ASSERT(nestedPlaylists >= 0);

        playlist->next();
        return;
    }

    if (media.playlist()) {
        if (nestedPlaylists < MAX_NESTED_PLAYLISTS) {
            nestedPlaylists++;
            Q_ASSERT(!pendingPlaylist.playlist());

            // disconnect current playlist
            disconnectPlaylist();
            // new playlist signals are connected
            // in the call to setPlaylist() in _q_handlePlaylistLoaded()
            playlist = media.playlist();
            emit q->currentMediaChanged(media);
            _q_handlePlaylistLoaded();
            return;
        } else if (playlist) {
            playlist->next();
        }
        return;
    }

    const QMediaPlayer::State currentState = state;

    setMedia(media, nullptr);

    if (!media.isNull()) {
        switch (currentState) {
        case QMediaPlayer::PlayingState:
            control->play();
            break;
        case QMediaPlayer::PausedState:
            control->pause();
            break;
        default:
            break;
        }
    }

    _q_stateChanged(control->state());
}

void QMediaPlayerPrivate::_q_playlistDestroyed()
{
    playlist = nullptr;
    setMedia(QMediaContent(), nullptr);
}

void QMediaPlayerPrivate::setMedia(const QMediaContent &media, QIODevice *stream)
{
    Q_Q(QMediaPlayer);

    if (!control)
        return;

    QScopedPointer<QFile> file;

    // Backends can't play qrc files directly.
    // If the backend supports StreamPlayback, we pass a QFile for that resource.
    // If it doesn't, we copy the data to a temporary file and pass its path.
    if (!media.isNull() && !stream && media.canonicalUrl().scheme() == QLatin1String("qrc")) {
        qrcMedia = media;

        file.reset(new QFile(QLatin1Char(':') + media.canonicalUrl().path()));
        if (!file->open(QFile::ReadOnly)) {
            QMetaObject::invokeMethod(q, "_q_error", Qt::QueuedConnection,
                                      Q_ARG(int, QMediaPlayer::ResourceError),
                                      Q_ARG(QString, QMediaPlayer::tr("Attempting to play invalid Qt resource")));
            QMetaObject::invokeMethod(q, "_q_mediaStatusChanged", Qt::QueuedConnection,
                                      Q_ARG(QMediaPlayer::MediaStatus, QMediaPlayer::InvalidMedia));
            file.reset();
            // Ignore the next NoMedia status change, we just want to clear the current media
            // on the backend side since we can't load the new one and we want to be in the
            // InvalidMedia status.
            ignoreNextStatusChange = QMediaPlayer::NoMedia;
            control->setMedia(QMediaContent(), nullptr);

        } else if (hasStreamPlaybackFeature) {
            control->setMedia(media, file.data());
        } else {
#if QT_CONFIG(temporaryfile)
            QTemporaryFile *tempFile = new QTemporaryFile;

            // Preserve original file extension, some backends might not load the file if it doesn't
            // have an extension.
            const QString suffix = QFileInfo(*file).suffix();
            if (!suffix.isEmpty())
                tempFile->setFileTemplate(tempFile->fileTemplate() + QLatin1Char('.') + suffix);

            // Copy the qrc data into the temporary file
            tempFile->open();
            char buffer[4096];
            while (true) {
                qint64 len = file->read(buffer, sizeof(buffer));
                if (len < 1)
                    break;
                tempFile->write(buffer, len);
            }
            tempFile->close();

            file.reset(tempFile);
            control->setMedia(QMediaContent(QUrl::fromLocalFile(file->fileName())), nullptr);
#else
            qWarning("Qt was built with -no-feature-temporaryfile: playback from resource file is not supported!");
#endif
        }
    } else {
        qrcMedia = QMediaContent();
        control->setMedia(media, stream);
    }

    qrcFile.swap(file); // Cleans up any previous file
}

void QMediaPlayerPrivate::_q_handleMediaChanged(const QMediaContent &media)
{
    Q_Q(QMediaPlayer);

    emit q->currentMediaChanged(qrcMedia.isNull() ? media : qrcMedia);
}

void QMediaPlayerPrivate::setPlaylist(QMediaPlaylist *pls)
{
    disconnectPlaylist();
    playlist = pls;

    setPlaylistMedia();
}

void QMediaPlayerPrivate::setPlaylistMedia()
{
    // This function loads current playlist media into backend.
    // If current media is a playlist, the function recursively
    // loads media from the playlist.
    // It also makes sure the correct playlist signals are connected.
    Q_Q(QMediaPlayer);

    if (playlist) {
        connectPlaylist();
        if (playlist->currentMedia().playlist()) {
            if (nestedPlaylists < MAX_NESTED_PLAYLISTS) {
                emit q->currentMediaChanged(playlist->currentMedia());
                // rewind nested playlist to start
                playlist->currentMedia().playlist()->setCurrentIndex(0);
                nestedPlaylists++;
                setPlaylist(playlist->currentMedia().playlist());
            } else {
                playlist->next();
            }
            return;
        } else {
            // If we've just switched to a new playlist,
            // then last emitted currentMediaChanged was a playlist.
            // Make sure we emit currentMediaChanged if new playlist has
            // the same media as the previous one:
            // sample.m3u
            //      test.wav     -- processed by backend
            //      nested.m3u   -- processed by frontend
            //          test.wav -- processed by backend,
            //                      media is not changed,
            //                      frontend needs to emit currentMediaChanged
            bool isSameMedia = (q->currentMedia() == playlist->currentMedia());
            setMedia(playlist->currentMedia(), nullptr);
            if (isSameMedia) {
                emit q->currentMediaChanged(q->currentMedia());
            }
        }
    } else {
        setMedia(QMediaContent(), nullptr);
    }
}

void QMediaPlayerPrivate::loadPlaylist()
{
    Q_Q(QMediaPlayer);
    Q_ASSERT(pendingPlaylist.isNull());

    // Do not load a playlist if there are more than MAX_NESTED_PLAYLISTS in the chain already,
    // or if the playlist URL is already in the chain, i.e. do not allow recursive playlists and loops.
    if (nestedPlaylists < MAX_NESTED_PLAYLISTS && !q->currentMedia().canonicalUrl().isEmpty() && !isInChain(q->currentMedia().canonicalUrl())) {
        pendingPlaylist = QMediaContent(new QMediaPlaylist, q->currentMedia().canonicalUrl(), true);
        QObject::connect(pendingPlaylist.playlist(), SIGNAL(loaded()), q, SLOT(_q_handlePlaylistLoaded()));
        QObject::connect(pendingPlaylist.playlist(), SIGNAL(loadFailed()), q, SLOT(_q_handlePlaylistLoadFailed()));
        pendingPlaylist.playlist()->load(pendingPlaylist.canonicalRequest());
    } else if (playlist) {
        playlist->next();
    }
}

void QMediaPlayerPrivate::disconnectPlaylist()
{
    Q_Q(QMediaPlayer);
    if (playlist) {
        QObject::disconnect(playlist, SIGNAL(currentMediaChanged(QMediaContent)),
                            q, SLOT(_q_updateMedia(QMediaContent)));
        QObject::disconnect(playlist, SIGNAL(destroyed()), q, SLOT(_q_playlistDestroyed()));
        q->unbind(playlist);
    }
}

void QMediaPlayerPrivate::connectPlaylist()
{
    Q_Q(QMediaPlayer);
    if (playlist) {
        q->bind(playlist);
        QObject::connect(playlist, SIGNAL(currentMediaChanged(QMediaContent)),
                         q, SLOT(_q_updateMedia(QMediaContent)));
        QObject::connect(playlist, SIGNAL(destroyed()), q, SLOT(_q_playlistDestroyed()));
    }
}

void QMediaPlayerPrivate::_q_handlePlaylistLoaded()
{
    Q_Q(QMediaPlayer);

    if (pendingPlaylist.playlist()) {
        Q_ASSERT(!q->currentMedia().playlist());
        // if there is an active playlist
        if (playlist) {
            Q_ASSERT(playlist->currentIndex() >= 0);
            disconnectPlaylist();
            playlist->insertMedia(playlist->currentIndex() + 1, pendingPlaylist);
            playlist->removeMedia(playlist->currentIndex());
            nestedPlaylists++;
        } else {
            Q_ASSERT(!rootMedia.playlist());
            rootMedia = pendingPlaylist;
            emit q->mediaChanged(rootMedia);
        }

        playlist = pendingPlaylist.playlist();
        emit q->currentMediaChanged(pendingPlaylist);
    }
    pendingPlaylist = QMediaContent();

    playlist->next();
    setPlaylistMedia();

    switch (state) {
    case QMediaPlayer::PausedState:
        control->pause();
        break;
    case QMediaPlayer::PlayingState:
        control->play();
        break;
    case QMediaPlayer::StoppedState:
        break;
    }
}

void QMediaPlayerPrivate::_q_handlePlaylistLoadFailed()
{
    pendingPlaylist = QMediaContent();

    if (!control)
        return;

    if (playlist)
        playlist->next();
    else
        setMedia(QMediaContent(), nullptr);
}

static QMediaService *playerService(QMediaPlayer::Flags flags)
{
    QMediaServiceProvider *provider = QMediaServiceProvider::defaultServiceProvider();
    if (flags) {
        QMediaServiceProviderHint::Features features = 0;
        if (flags & QMediaPlayer::LowLatency)
            features |= QMediaServiceProviderHint::LowLatencyPlayback;

        if (flags & QMediaPlayer::StreamPlayback)
            features |= QMediaServiceProviderHint::StreamPlayback;

        if (flags & QMediaPlayer::VideoSurface)
            features |= QMediaServiceProviderHint::VideoSurface;

        return provider->requestService(Q_MEDIASERVICE_MEDIAPLAYER,
                                        QMediaServiceProviderHint(features));
    }

    return provider->requestService(Q_MEDIASERVICE_MEDIAPLAYER);
}


/*!
    Construct a QMediaPlayer instance
    parented to \a parent and with \a flags.
*/

QMediaPlayer::QMediaPlayer(QObject *parent, QMediaPlayer::Flags flags):
    QMediaObject(*new QMediaPlayerPrivate,
                 parent,
                 playerService(flags))
{
    Q_D(QMediaPlayer);

    d->provider = QMediaServiceProvider::defaultServiceProvider();
    if (d->service == nullptr) {
        d->error = ServiceMissingError;
    } else {
        d->control = qobject_cast<QMediaPlayerControl*>(d->service->requestControl(QMediaPlayerControl_iid));
        d->networkAccessControl = qobject_cast<QMediaNetworkAccessControl*>(d->service->requestControl(QMediaNetworkAccessControl_iid));
        if (d->control != nullptr) {
            connect(d->control, SIGNAL(mediaChanged(QMediaContent)), SLOT(_q_handleMediaChanged(QMediaContent)));
            connect(d->control, SIGNAL(stateChanged(QMediaPlayer::State)), SLOT(_q_stateChanged(QMediaPlayer::State)));
            connect(d->control, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),
                    SLOT(_q_mediaStatusChanged(QMediaPlayer::MediaStatus)));
            connect(d->control, SIGNAL(error(int,QString)), SLOT(_q_error(int,QString)));

            connect(d->control, &QMediaPlayerControl::durationChanged, this, &QMediaPlayer::durationChanged);
            connect(d->control, &QMediaPlayerControl::positionChanged, this, &QMediaPlayer::positionChanged);
            connect(d->control, &QMediaPlayerControl::audioAvailableChanged, this, &QMediaPlayer::audioAvailableChanged);
            connect(d->control, &QMediaPlayerControl::videoAvailableChanged, this, &QMediaPlayer::videoAvailableChanged);
            connect(d->control, &QMediaPlayerControl::volumeChanged, this, &QMediaPlayer::volumeChanged);
            connect(d->control, &QMediaPlayerControl::mutedChanged, this, &QMediaPlayer::mutedChanged);
            connect(d->control, &QMediaPlayerControl::seekableChanged, this, &QMediaPlayer::seekableChanged);
            connect(d->control, &QMediaPlayerControl::playbackRateChanged, this, &QMediaPlayer::playbackRateChanged);
            connect(d->control, &QMediaPlayerControl::bufferStatusChanged, this, &QMediaPlayer::bufferStatusChanged);

            d->state = d->control->state();
            d->status = d->control->mediaStatus();

            if (d->state == PlayingState)
                addPropertyWatch("position");

            if (d->status == StalledMedia || d->status == BufferingMedia)
                addPropertyWatch("bufferStatus");

            d->hasStreamPlaybackFeature = d->provider->supportedFeatures(d->service).testFlag(QMediaServiceProviderHint::StreamPlayback);

            d->audioRoleControl = qobject_cast<QAudioRoleControl*>(d->service->requestControl(QAudioRoleControl_iid));
            if (d->audioRoleControl) {
                connect(d->audioRoleControl, &QAudioRoleControl::audioRoleChanged,
                        this, &QMediaPlayer::audioRoleChanged);

                d->customAudioRoleControl = qobject_cast<QCustomAudioRoleControl *>(
                        d->service->requestControl(QCustomAudioRoleControl_iid));
                if (d->customAudioRoleControl) {
                    connect(d->customAudioRoleControl,
                            &QCustomAudioRoleControl::customAudioRoleChanged,
                            this,
                            &QMediaPlayer::customAudioRoleChanged);
                }
            }
        }
        if (d->networkAccessControl != nullptr) {
            connect(d->networkAccessControl, &QMediaNetworkAccessControl::configurationChanged,
                    this, &QMediaPlayer::networkConfigurationChanged);
        }
    }
}


/*!
    Destroys the player object.
*/

QMediaPlayer::~QMediaPlayer()
{
    Q_D(QMediaPlayer);

    d->disconnectPlaylist();
    // Disconnect everything to prevent notifying
    // when a receiver is already destroyed.
    disconnect();

    if (d->service) {
        if (d->control)
            d->service->releaseControl(d->control);
        if (d->audioRoleControl)
            d->service->releaseControl(d->audioRoleControl);
        if (d->customAudioRoleControl)
            d->service->releaseControl(d->customAudioRoleControl);

        d->provider->releaseService(d->service);
    }
}

QMediaContent QMediaPlayer::media() const
{
    Q_D(const QMediaPlayer);

    return d->rootMedia;
}

/*!
    Returns the stream source of media data.

    This is only valid if a stream was passed to setMedia().

    \sa setMedia()
*/

const QIODevice *QMediaPlayer::mediaStream() const
{
    Q_D(const QMediaPlayer);

    // When playing a resource file, we might have passed a QFile to the backend. Hide it from
    // the user.
    if (d->control && d->qrcMedia.isNull())
        return d->control->mediaStream();

    return nullptr;
}

QMediaPlaylist *QMediaPlayer::playlist() const
{
    Q_D(const QMediaPlayer);

    return d->rootMedia.playlist();
}

QMediaContent QMediaPlayer::currentMedia() const
{
    Q_D(const QMediaPlayer);

    // When playing a resource file, don't return the backend's current media, which
    // can be a temporary file.
    if (!d->qrcMedia.isNull())
        return d->qrcMedia;

    if (d->control)
        return d->control->media();

    return QMediaContent();
}

void QMediaPlayer::setPlaylist(QMediaPlaylist *playlist)
{
    QMediaContent m(playlist, QUrl(), false);
    setMedia(m);
}

/*!
    Sets the network access points for remote media playback.
    \a configurations contains, in ascending preferential order, a list of
    configuration  that can be used for network access.

    This will invalidate the choice of previous configurations.
*/
void QMediaPlayer::setNetworkConfigurations(const QList<QNetworkConfiguration> &configurations)
{
    Q_D(QMediaPlayer);

    if (d->networkAccessControl)
        d->networkAccessControl->setConfigurations(configurations);
}

QMediaPlayer::State QMediaPlayer::state() const
{
    Q_D(const QMediaPlayer);

    // In case if EndOfMedia status is already received
    // but state is not.
    if (d->control != nullptr
        && d->status == QMediaPlayer::EndOfMedia
        && d->state != d->control->state()) {
        return d->control->state();
    }

    return d->state;
}

QMediaPlayer::MediaStatus QMediaPlayer::mediaStatus() const
{
    return d_func()->status;
}

qint64 QMediaPlayer::duration() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->duration();

    return -1;
}

qint64 QMediaPlayer::position() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->position();

    return 0;
}

int QMediaPlayer::volume() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->volume();

    return 0;
}

bool QMediaPlayer::isMuted() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->isMuted();

    return false;
}

int QMediaPlayer::bufferStatus() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->bufferStatus();

    return 0;
}

bool QMediaPlayer::isAudioAvailable() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->isAudioAvailable();

    return false;
}

bool QMediaPlayer::isVideoAvailable() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->isVideoAvailable();

    return false;
}

bool QMediaPlayer::isSeekable() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->isSeekable();

    return false;
}

qreal QMediaPlayer::playbackRate() const
{
    Q_D(const QMediaPlayer);

    if (d->control != nullptr)
        return d->control->playbackRate();

    return 0.0;
}

/*!
    Returns the current error state.
*/

QMediaPlayer::Error QMediaPlayer::error() const
{
    return d_func()->error;
}

QString QMediaPlayer::errorString() const
{
    return d_func()->errorString;
}

/*!
    Returns the current network access point  in use.
    If a default contructed QNetworkConfiguration is returned
    this feature is not available or that none of the
    current supplied configurations are in use.
*/
QNetworkConfiguration QMediaPlayer::currentNetworkConfiguration() const
{
    Q_D(const QMediaPlayer);

    if (d->networkAccessControl)
        return d_func()->networkAccessControl->currentConfiguration();

    return QNetworkConfiguration();
}

//public Q_SLOTS:
/*!
    Start or resume playing the current source.
*/

void QMediaPlayer::play()
{
    Q_D(QMediaPlayer);

    if (d->control == nullptr) {
        QMetaObject::invokeMethod(this, "_q_error", Qt::QueuedConnection,
                                    Q_ARG(int, QMediaPlayer::ServiceMissingError),
                                    Q_ARG(QString, tr("The QMediaPlayer object does not have a valid service")));
        return;
    }

    //if playlist control is available, the service should advance itself
    if (d->rootMedia.playlist() && !d->rootMedia.playlist()->isEmpty()) {
        // switch to playing state
        if (d->state != QMediaPlayer::PlayingState)
            d->_q_stateChanged(QMediaPlayer::PlayingState);

        if (d->rootMedia.playlist()->currentIndex() == -1) {
            if (d->playlist != d->rootMedia.playlist())
                d->setPlaylist(d->rootMedia.playlist());
            Q_ASSERT(d->playlist == d->rootMedia.playlist());

            emit currentMediaChanged(d->rootMedia);
            d->playlist->setCurrentIndex(0);
        }
    }

    // Reset error conditions
    d->error = NoError;
    d->errorString = QString();

    d->control->play();
}

/*!
    Pause playing the current source.
*/

void QMediaPlayer::pause()
{
    Q_D(QMediaPlayer);

    if (d->control != nullptr)
        d->control->pause();
}

/*!
    Stop playing, and reset the play position to the beginning.
*/

void QMediaPlayer::stop()
{
    Q_D(QMediaPlayer);

    if (d->control != nullptr)
        d->control->stop();

    // If media player didn't stop in response to control.
    // This happens if we have an active playlist and control
    // media status is
    // QMediaPlayer::LoadingMedia, QMediaPlayer::InvalidMedia, or QMediaPlayer::EndOfMedia
    // see QMediaPlayerPrivate::_q_stateChanged()
    if (d->playlist && d->state != QMediaPlayer::StoppedState) {
        d->state = QMediaPlayer::StoppedState;
        removePropertyWatch("position");
        emit stateChanged(QMediaPlayer::StoppedState);
    }
}

void QMediaPlayer::setPosition(qint64 position)
{
    Q_D(QMediaPlayer);

    if (d->control == nullptr)
        return;

    d->control->setPosition(qMax(position, 0ll));
}

void QMediaPlayer::setVolume(int v)
{
    Q_D(QMediaPlayer);

    if (d->control == nullptr)
        return;

    int clamped = qBound(0, v, 100);
    if (clamped == volume())
        return;

    d->control->setVolume(clamped);
}

void QMediaPlayer::setMuted(bool muted)
{
    Q_D(QMediaPlayer);

    if (d->control == nullptr || muted == isMuted())
        return;

    d->control->setMuted(muted);
}

void QMediaPlayer::setPlaybackRate(qreal rate)
{
    Q_D(QMediaPlayer);

    if (d->control != nullptr)
        d->control->setPlaybackRate(rate);
}

/*!
    Sets the current \a media source.

    If a \a stream is supplied; media data will be read from it instead of resolving the media
    source. In this case the media source may still be used to resolve additional information
    about the media such as mime type. The \a stream must be open and readable.

    Setting the media to a null QMediaContent will cause the player to discard all
    information relating to the current media source and to cease all I/O operations related
    to that media.

    \note This function returns immediately after recording the specified source of the media.
    It does not wait for the media to finish loading and does not check for errors. Listen for
    the mediaStatusChanged() and error() signals to be notified when the media is loaded and
    when an error occurs during loading.

    Since Qt 5.12.2, the url scheme \c gst-pipeline provides custom pipelines
    for the GStreamer backend.

    \snippet multimedia-snippets/media.cpp Pipeline

    If the pipeline contains a video sink element named \c qtvideosink,
    current QVideoWidget can be used to render the video.

    If the pipeline contains appsrc element, it will be used to push data from \a stream.

    \snippet multimedia-snippets/media.cpp Pipeline appsrc
*/

void QMediaPlayer::setMedia(const QMediaContent &media, QIODevice *stream)
{
    Q_D(QMediaPlayer);
    stop();

    QMediaContent oldMedia = d->rootMedia;
    d->disconnectPlaylist();
    d->playlist = nullptr;
    d->rootMedia = media;
    d->nestedPlaylists = 0;

    if (oldMedia != media)
        emit mediaChanged(d->rootMedia);

    if (media.playlist()) {
        // reset playlist to the 1st item
        media.playlist()->setCurrentIndex(0);
        d->setPlaylist(media.playlist());
    } else {
        d->setMedia(media, stream);
    }
}

/*!
    \internal
*/

bool QMediaPlayer::bind(QObject *obj)
{
    return QMediaObject::bind(obj);
}

/*!
    \internal
*/

void QMediaPlayer::unbind(QObject *obj)
{
    QMediaObject::unbind(obj);
}

/*!
    Returns the level of support a media player has for a \a mimeType and a set of \a codecs.

    The \a flags argument allows additional requirements such as performance indicators to be
    specified.
*/
QMultimedia::SupportEstimate QMediaPlayer::hasSupport(const QString &mimeType,
                                               const QStringList& codecs,
                                               Flags flags)
{
    return QMediaServiceProvider::defaultServiceProvider()->hasSupport(QByteArray(Q_MEDIASERVICE_MEDIAPLAYER),
                                                                    mimeType,
                                                                    codecs,
                                                                    flags);
}

/*!
    \deprecated
    Returns a list of MIME types supported by the media player.

    The \a flags argument causes the resultant list to be restricted to MIME types which can be supported
    given additional requirements, such as performance indicators.

    This function may not return useful results on some platforms, and support for a specific file of a
    given mime type is not guaranteed even if the mime type is in general supported.  In addition, in some
    cases this function will need to load all available media plugins and query them for their support, which
    may take some time.
*/
QStringList QMediaPlayer::supportedMimeTypes(Flags flags)
{
    return QMediaServiceProvider::defaultServiceProvider()->supportedMimeTypes(QByteArray(Q_MEDIASERVICE_MEDIAPLAYER),
                                                                               flags);
}

/*!
    \fn void QMediaPlayer::setVideoOutput(QVideoWidget* output)

    Attach a QVideoWidget video \a output to the media player.

    If the media player has already video output attached,
    it will be replaced with a new one.
*/
void QMediaPlayer::setVideoOutput(QVideoWidget *output)
{
    Q_D(QMediaPlayer);

    if (d->videoOutput)
        unbind(d->videoOutput);

    // We don't know (in this library) that QVideoWidget inherits QObject
    QObject *outputObject = reinterpret_cast<QObject*>(output);

    d->videoOutput = outputObject && bind(outputObject) ? outputObject : nullptr;
}

/*!
    \fn void QMediaPlayer::setVideoOutput(QGraphicsVideoItem* output)

    Attach a QGraphicsVideoItem video \a output to the media player.

    If the media player has already video output attached,
    it will be replaced with a new one.
*/
void QMediaPlayer::setVideoOutput(QGraphicsVideoItem *output)
{
    Q_D(QMediaPlayer);

    if (d->videoOutput)
        unbind(d->videoOutput);

    // We don't know (in this library) that QGraphicsVideoItem (multiply) inherits QObject
    // but QObject inheritance depends on QObject coming first, so try this out.
    QObject *outputObject = reinterpret_cast<QObject*>(output);

    d->videoOutput = outputObject && bind(outputObject) ? outputObject : nullptr;
}

/*!
    Sets a video \a surface as the video output of a media player.

    If a video output has already been set on the media player the new surface
    will replace it.
*/

void QMediaPlayer::setVideoOutput(QAbstractVideoSurface *surface)
{
    Q_D(QMediaPlayer);

    d->surfaceOutput.setVideoSurface(surface);

    if (d->videoOutput != &d->surfaceOutput) {
        if (d->videoOutput)
            unbind(d->videoOutput);

        d->videoOutput = nullptr;

        if (surface && bind(&d->surfaceOutput))
            d->videoOutput =  &d->surfaceOutput;
    }  else if (!surface) {
        //unbind the surfaceOutput if null surface is set
        unbind(&d->surfaceOutput);
        d->videoOutput = nullptr;
    }
}

/*! \reimp */
QMultimedia::AvailabilityStatus QMediaPlayer::availability() const
{
    Q_D(const QMediaPlayer);

    if (!d->control)
        return QMultimedia::ServiceMissing;

    return QMediaObject::availability();
}

QAudio::Role QMediaPlayer::audioRole() const
{
    Q_D(const QMediaPlayer);

    if (d->audioRoleControl != nullptr)
        return d->audioRoleControl->audioRole();

    return QAudio::UnknownRole;
}

void QMediaPlayer::setAudioRole(QAudio::Role audioRole)
{
    Q_D(QMediaPlayer);

    if (d->audioRoleControl) {
        if (d->customAudioRoleControl != nullptr && d->audioRoleControl->audioRole() != audioRole) {
            d->customAudioRoleControl->setCustomAudioRole(QString());
        }

        d->audioRoleControl->setAudioRole(audioRole);
    }
}

/*!
    Returns a list of supported audio roles.

    If setting the audio role is not supported, an empty list is returned.

    \since 5.6
    \sa audioRole
*/
QList<QAudio::Role> QMediaPlayer::supportedAudioRoles() const
{
    Q_D(const QMediaPlayer);

    if (d->audioRoleControl)
        return d->audioRoleControl->supportedAudioRoles();

    return QList<QAudio::Role>();
}

QString QMediaPlayer::customAudioRole() const
{
    Q_D(const QMediaPlayer);

    if (audioRole() != QAudio::CustomRole)
        return QString();

    if (d->customAudioRoleControl != nullptr)
        return d->customAudioRoleControl->customAudioRole();

    return QString();
}

void QMediaPlayer::setCustomAudioRole(const QString &audioRole)
{
    Q_D(QMediaPlayer);

    if (d->customAudioRoleControl) {
        Q_ASSERT(d->audioRoleControl);
        setAudioRole(QAudio::CustomRole);
        d->customAudioRoleControl->setCustomAudioRole(audioRole);
    }
}

/*!
    Returns a list of supported custom audio roles. An empty list may
    indicate that the supported custom audio roles aren't known. The
    list may not be complete.

    \since 5.11
    \sa customAudioRole
*/
QStringList QMediaPlayer::supportedCustomAudioRoles() const
{
    Q_D(const QMediaPlayer);

    if (d->customAudioRoleControl)
        return d->customAudioRoleControl->supportedCustomAudioRoles();

    return QStringList();
}

// Enums
/*!
    \enum QMediaPlayer::State

    Defines the current state of a media player.

    \value StoppedState The media player is not playing content, playback will begin from the start
    of the current track.
    \value PlayingState The media player is currently playing content.
    \value PausedState The media player has paused playback, playback of the current track will
    resume from the position the player was paused at.
*/

/*!
    \enum QMediaPlayer::MediaStatus

    Defines the status of a media player's current media.

    \value UnknownMediaStatus The status of the media cannot be determined.
    \value NoMedia The is no current media.  The player is in the StoppedState.
    \value LoadingMedia The current media is being loaded. The player may be in any state.
    \value LoadedMedia The current media has been loaded. The player is in the StoppedState.
    \value StalledMedia Playback of the current media has stalled due to insufficient buffering or
    some other temporary interruption.  The player is in the PlayingState or PausedState.
    \value BufferingMedia The player is buffering data but has enough data buffered for playback to
    continue for the immediate future.  The player is in the PlayingState or PausedState.
    \value BufferedMedia The player has fully buffered the current media.  The player is in the
    PlayingState or PausedState.
    \value EndOfMedia Playback has reached the end of the current media.  The player is in the
    StoppedState.
    \value InvalidMedia The current media cannot be played.  The player is in the StoppedState.
*/

/*!
    \enum QMediaPlayer::Error

    Defines a media player error condition.

    \value NoError No error has occurred.
    \value ResourceError A media resource couldn't be resolved.
    \value FormatError The format of a media resource isn't (fully) supported.  Playback may still
    be possible, but without an audio or video component.
    \value NetworkError A network error occurred.
    \value AccessDeniedError There are not the appropriate permissions to play a media resource.
    \value ServiceMissingError A valid playback service was not found, playback cannot proceed.
    \omitvalue MediaIsPlaylist
*/

// Signals
/*!
    \fn QMediaPlayer::error(QMediaPlayer::Error error)

    Signals that an \a error condition has occurred.

    \sa errorString()
*/

/*!
    \fn void QMediaPlayer::stateChanged(State state)

    Signal the \a state of the Player object has changed.
*/

/*!
    \fn QMediaPlayer::mediaStatusChanged(QMediaPlayer::MediaStatus status)

    Signals that the \a status of the current media has changed.

    \sa mediaStatus()
*/

/*!
    \fn void QMediaPlayer::mediaChanged(const QMediaContent &media);

    Signals that the media source has been changed to \a media.

    \sa media(), currentMediaChanged()
*/

/*!
    \fn void QMediaPlayer::currentMediaChanged(const QMediaContent &media);

    Signals that the current playing content has been changed to \a media.

    \sa currentMedia(), mediaChanged()
*/

/*!
    \fn void QMediaPlayer::playbackRateChanged(qreal rate);

    Signals the playbackRate has changed to \a rate.
*/

/*!
    \fn void QMediaPlayer::seekableChanged(bool seekable);

    Signals the \a seekable status of the player object has changed.
*/

/*!
    \fn void QMediaPlayer::audioRoleChanged(QAudio::Role role)

    Signals that the audio \a role of the media player has changed.

    \since 5.6
*/

/*!
    \fn void QMediaPlayer::customAudioRoleChanged(const QString &role)

    Signals that the audio \a role of the media player has changed.

    \since 5.11
*/

// Properties
/*!
    \property QMediaPlayer::state
    \brief the media player's playback state.

    By default this property is QMediaPlayer::Stopped

    \sa mediaStatus(), play(), pause(), stop()
*/

/*!
    \property QMediaPlayer::error
    \brief a string describing the last error condition.

    \sa error()
*/

/*!
    \property QMediaPlayer::media
    \brief the active media source being used by the player object.

    The player object will use the QMediaContent for selection of the content to
    be played.

    By default this property has a null QMediaContent.

    Setting this property to a null QMediaContent will cause the player to discard all
    information relating to the current media source and to cease all I/O operations related
    to that media.

    \sa QMediaContent, currentMedia()
*/

/*!
    \property QMediaPlayer::currentMedia
    \brief the current active media content being played by the player object.
    This value could be different from QMediaPlayer::media property if a playlist is used.
    In this case currentMedia indicates the current media content being processed
    by the player, while QMediaPlayer::media property contains the original playlist.

    \sa QMediaContent, media()
*/

/*!
    \property QMediaPlayer::playlist
    \brief the media playlist being used by the player object.

    The player object will use the current playlist item for selection of the content to
    be played.

    By default this property is set to null.

    If the media playlist is used as a source, QMediaPlayer::currentMedia is updated with
    a current playlist item. The current source should be selected with
    QMediaPlaylist::setCurrentIndex(int) instead of QMediaPlayer::setMedia(),
    otherwise the current playlist will be discarded.

    \sa QMediaContent
*/


/*!
    \property QMediaPlayer::mediaStatus
    \brief the status of the current media stream.

    The stream status describes how the playback of the current stream is
    progressing.

    By default this property is QMediaPlayer::NoMedia

    \sa state
*/

/*!
    \property QMediaPlayer::duration
    \brief the duration of the current media.

    The value is the total playback time in milliseconds of the current media.
    The value may change across the life time of the QMediaPlayer object and
    may not be available when initial playback begins, connect to the
    durationChanged() signal to receive status notifications.
*/

/*!
    \property QMediaPlayer::position
    \brief the playback position of the current media.

    The value is the current playback position, expressed in milliseconds since
    the beginning of the media. Periodically changes in the position will be
    indicated with the signal positionChanged(), the interval between updates
    can be set with QMediaObject's method setNotifyInterval().
*/

/*!
    \property QMediaPlayer::volume
    \brief the current playback volume.

    The playback volume is scaled linearly, ranging from \c 0 (silence) to \c 100 (full volume).
    Values outside this range will be clamped.

    By default the volume is \c 100.

    UI volume controls should usually be scaled nonlinearly. For example, using a logarithmic scale
    will produce linear changes in perceived loudness, which is what a user would normally expect
    from a volume control. See QAudio::convertVolume() for more details.
*/

/*!
    \property QMediaPlayer::muted
    \brief the muted state of the current media.

    The value will be true if the playback volume is muted; otherwise false.
*/

/*!
    \property QMediaPlayer::bufferStatus
    \brief the percentage of the temporary buffer filled before playback begins or resumes, from
    \c 0 (empty) to \c 100 (full).

    When the player object is buffering; this property holds the percentage of
    the temporary buffer that is filled. The buffer will need to reach 100%
    filled before playback can start or resume, at which time mediaStatus() will return
    BufferedMedia or BufferingMedia. If the value is anything lower than \c 100, mediaStatus() will
    return StalledMedia.

    \sa mediaStatus()
*/

/*!
    \property QMediaPlayer::audioAvailable
    \brief the audio availabilty status for the current media.

    As the life time of QMediaPlayer can be longer than the playback of one
    QMediaContent, this property may change over time, the
    audioAvailableChanged signal can be used to monitor it's status.
*/

/*!
    \property QMediaPlayer::videoAvailable
    \brief the video availability status for the current media.

    If available, the QVideoWidget class can be used to view the video. As the
    life time of QMediaPlayer can be longer than the playback of one
    QMediaContent, this property may change over time, the
    videoAvailableChanged signal can be used to monitor it's status.

    \sa QVideoWidget, QMediaContent
*/

/*!
    \property QMediaPlayer::seekable
    \brief the seek-able status of the current media

    If seeking is supported this property will be true; false otherwise. The
    status of this property may change across the life time of the QMediaPlayer
    object, use the seekableChanged signal to monitor changes.
*/

/*!
    \property QMediaPlayer::playbackRate
    \brief the playback rate of the current media.

    This value is a multiplier applied to the media's standard play rate. By
    default this value is 1.0, indicating that the media is playing at the
    standard pace. Values higher than 1.0 will increase the rate of play.
    Values less than zero can be set and indicate the media will rewind at the
    multiplier of the standard pace.

    Not all playback services support change of the playback rate. It is
    framework defined as to the status and quality of audio and video
    while fast forwarding or rewinding.
*/

/*!
    \property QMediaPlayer::audioRole
    \brief the role of the audio stream played by the media player.

    It can be set to specify the type of audio being played, allowing the system to make
    appropriate decisions when it comes to volume, routing or post-processing.

    The audio role must be set before calling setMedia().

    customAudioRole is cleared when this property is set to anything other than
    QAudio::CustomRole.

    \since 5.6
    \sa supportedAudioRoles()
*/

/*!
    \property QMediaPlayer::customAudioRole
    \brief the role of the audio stream played by the media player.

    It can be set to specify the type of audio being played when the backend supports
    audio roles unknown to Qt. Specifying a role allows the system to make appropriate
    decisions when it comes to volume, routing or post-processing.

    The audio role must be set before calling setMedia().

    audioRole is set to QAudio::CustomRole when this property is set.

    \since 5.11
    \sa supportedCustomAudioRoles()
*/

/*!
    \fn void QMediaPlayer::durationChanged(qint64 duration)

    Signal the duration of the content has changed to \a duration, expressed in milliseconds.
*/

/*!
    \fn void QMediaPlayer::positionChanged(qint64 position)

    Signal the position of the content has changed to \a position, expressed in
    milliseconds.
*/

/*!
    \fn void QMediaPlayer::volumeChanged(int volume)

    Signal the playback volume has changed to \a volume.
*/

/*!
    \fn void QMediaPlayer::mutedChanged(bool muted)

    Signal the mute state has changed to \a muted.
*/

/*!
    \fn void QMediaPlayer::videoAvailableChanged(bool videoAvailable)

    Signal the availability of visual content has changed to \a videoAvailable.
*/

/*!
    \fn void QMediaPlayer::audioAvailableChanged(bool available)

    Signals the availability of audio content has changed to \a available.
*/

/*!
    \fn void QMediaPlayer::bufferStatusChanged(int percentFilled)

    Signal the amount of the local buffer filled as a percentage by \a percentFilled.
*/

/*!
   \fn void QMediaPlayer::networkConfigurationChanged(const QNetworkConfiguration &configuration)

    Signal that the active in use network access point  has been changed to \a configuration and all subsequent network access will use this \a configuration.
*/

/*!
    \enum QMediaPlayer::Flag

    \value LowLatency       The player is expected to be used with simple audio formats,
            but playback should start without significant delay.
            Such playback service can be used for beeps, ringtones, etc.

    \value StreamPlayback   The player is expected to play QIODevice based streams.
            If passed to QMediaPlayer constructor, the service supporting
            streams playback will be chosen.

    \value VideoSurface     The player is expected to be able to render to a
            QAbstractVideoSurface \l {setVideoOutput()}{output}.
*/

QT_END_NAMESPACE

#include "moc_qmediaplayer.cpp"
