/****************************************************************************
**
** 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 "qandroidmediaplayer_p.h"
#include "androidmediaplayer_p.h"
#include "qandroidvideooutput_p.h"
#include "qandroidmetadata_p.h"
#include "qandroidaudiooutput_p.h"
#include "qaudiooutput.h"

#include <qloggingcategory.h>

QT_BEGIN_NAMESPACE

Q_LOGGING_CATEGORY(lcMediaPlayer, "qt.multimedia.mediaplayer.android")

class StateChangeNotifier
{
public:
    StateChangeNotifier(QAndroidMediaPlayer *mp)
        : mControl(mp)
        , mPreviousState(mp->state())
        , mPreviousMediaStatus(mp->mediaStatus())
    {
        ++mControl->mActiveStateChangeNotifiers;
    }

    ~StateChangeNotifier()
    {
        if (--mControl->mActiveStateChangeNotifiers)
            return;

        if (mPreviousMediaStatus != mControl->mediaStatus())
            Q_EMIT mControl->mediaStatusChanged(mControl->mediaStatus());

        if (mPreviousState != mControl->state())
            Q_EMIT mControl->stateChanged(mControl->state());
    }

private:
    QAndroidMediaPlayer *mControl;
    QMediaPlayer::PlaybackState mPreviousState;
    QMediaPlayer::MediaStatus mPreviousMediaStatus;
};

QAndroidMediaPlayer::QAndroidMediaPlayer(QMediaPlayer *parent)
    : QPlatformMediaPlayer(parent),
      mMediaPlayer(new AndroidMediaPlayer),
      mState(AndroidMediaPlayer::Uninitialized)
{
    // Set seekable to True by default. It changes if MEDIA_INFO_NOT_SEEKABLE is received
    seekableChanged(true);
    connect(mMediaPlayer, &AndroidMediaPlayer::bufferingChanged, this,
            &QAndroidMediaPlayer::onBufferingChanged);
    connect(mMediaPlayer, &AndroidMediaPlayer::info, this, &QAndroidMediaPlayer::onInfo);
    connect(mMediaPlayer, &AndroidMediaPlayer::error, this, &QAndroidMediaPlayer::onError);
    connect(mMediaPlayer, &AndroidMediaPlayer::stateChanged, this,
            &QAndroidMediaPlayer::onStateChanged);
    connect(mMediaPlayer, &AndroidMediaPlayer::videoSizeChanged, this,
            &QAndroidMediaPlayer::onVideoSizeChanged);
    connect(mMediaPlayer, &AndroidMediaPlayer::progressChanged, this,
            &QAndroidMediaPlayer::positionChanged);
    connect(mMediaPlayer, &AndroidMediaPlayer::durationChanged, this,
            &QAndroidMediaPlayer::durationChanged);
    connect(mMediaPlayer, &AndroidMediaPlayer::tracksInfoChanged, this,
            &QAndroidMediaPlayer::updateTrackInfo);
}

QAndroidMediaPlayer::~QAndroidMediaPlayer()
{
    mMediaPlayer->disconnect();
    mMediaPlayer->release();
    delete mMediaPlayer;
}

qint64 QAndroidMediaPlayer::duration() const
{
    if (mediaStatus() == QMediaPlayer::NoMedia)
        return 0;

    if ((mState & (AndroidMediaPlayer::Prepared
                   | AndroidMediaPlayer::Started
                   | AndroidMediaPlayer::Paused
                   | AndroidMediaPlayer::Stopped
                   | AndroidMediaPlayer::PlaybackCompleted)) == 0) {
        return 0;
    }

    return mMediaPlayer->getDuration();
}

qint64 QAndroidMediaPlayer::position() const
{
    if (mediaStatus() == QMediaPlayer::EndOfMedia)
        return duration();

    if ((mState & (AndroidMediaPlayer::Prepared
                   | AndroidMediaPlayer::Started
                   | AndroidMediaPlayer::Paused
                   | AndroidMediaPlayer::PlaybackCompleted))) {
        return mMediaPlayer->getCurrentPosition();
    }

    return (mPendingPosition == -1) ? 0 : mPendingPosition;
}

void QAndroidMediaPlayer::setPosition(qint64 position)
{
    if (!isSeekable())
        return;

    const int seekPosition = (position > INT_MAX) ? INT_MAX : position;

    qint64 currentPosition = mMediaPlayer->getCurrentPosition();
    if (seekPosition == currentPosition) {
        // update position - will send a new frame of this position
        // for consistency with other platforms
        mMediaPlayer->seekTo(seekPosition);
        return;
    }
    StateChangeNotifier notifier(this);

    if (mediaStatus() == QMediaPlayer::EndOfMedia)
        setMediaStatus(QMediaPlayer::LoadedMedia);

    if ((mState & (AndroidMediaPlayer::Prepared
                   | AndroidMediaPlayer::Started
                   | AndroidMediaPlayer::Paused
                   | AndroidMediaPlayer::PlaybackCompleted)) == 0) {
        mPendingPosition = seekPosition;
    } else {
        mMediaPlayer->seekTo(seekPosition);

        if (mPendingPosition != -1) {
            mPendingPosition = -1;
        }
    }

    Q_EMIT positionChanged(seekPosition);
}

void QAndroidMediaPlayer::setVolume(float volume)
{
    if ((mState & (AndroidMediaPlayer::Idle
                   | AndroidMediaPlayer::Initialized
                   | AndroidMediaPlayer::Stopped
                   | AndroidMediaPlayer::Prepared
                   | AndroidMediaPlayer::Started
                   | AndroidMediaPlayer::Paused
                   | AndroidMediaPlayer::PlaybackCompleted)) == 0) {
        mPendingVolume = volume;
        return;
    }

    mMediaPlayer->setVolume(qRound(volume*100.));
    mPendingVolume = -1;
}

void QAndroidMediaPlayer::setMuted(bool muted)
{
    if ((mState & (AndroidMediaPlayer::Idle
                   | AndroidMediaPlayer::Initialized
                   | AndroidMediaPlayer::Stopped
                   | AndroidMediaPlayer::Prepared
                   | AndroidMediaPlayer::Started
                   | AndroidMediaPlayer::Paused
                   | AndroidMediaPlayer::PlaybackCompleted)) == 0) {
        mPendingMute = muted;
        return;
    }

    mMediaPlayer->setMuted(muted);
    mPendingMute = -1;
}

QMediaMetaData QAndroidMediaPlayer::metaData() const
{
    return QAndroidMetaData::extractMetadata(mMediaContent);
}

float QAndroidMediaPlayer::bufferProgress() const
{
    return mBufferFilled ? 1. : mBufferPercent;
}

bool QAndroidMediaPlayer::isAudioAvailable() const
{
    return mAudioAvailable;
}

bool QAndroidMediaPlayer::isVideoAvailable() const
{
    return mVideoAvailable;
}

QMediaTimeRange QAndroidMediaPlayer::availablePlaybackRanges() const
{
    return mAvailablePlaybackRange;
}

void QAndroidMediaPlayer::updateAvailablePlaybackRanges()
{
    if (mBuffering) {
        const qint64 pos = position();
        const qint64 end = (duration() / 100) * mBufferPercent;
        mAvailablePlaybackRange.addInterval(pos, end);
    } else if (isSeekable()) {
        mAvailablePlaybackRange = QMediaTimeRange(0, duration());
    } else {
        mAvailablePlaybackRange = QMediaTimeRange();
    }

// ####    Q_EMIT availablePlaybackRangesChanged(mAvailablePlaybackRange);
}

qreal QAndroidMediaPlayer::playbackRate() const
{
    return mCurrentPlaybackRate;
}

void QAndroidMediaPlayer::setPlaybackRate(qreal rate)
{
    if (mState != AndroidMediaPlayer::Started) {
        // If video isn't playing, changing speed rate may start it automatically
        // It need to be postponed
        if (mCurrentPlaybackRate != rate) {
            mCurrentPlaybackRate = rate;
            mHasPendingPlaybackRate = true;
            Q_EMIT playbackRateChanged(rate);
        }
        return;
    }

    if (mMediaPlayer->setPlaybackRate(rate)) {
        mCurrentPlaybackRate = rate;
        Q_EMIT playbackRateChanged(rate);
    }
}

QUrl QAndroidMediaPlayer::media() const
{
    return mMediaContent;
}

const QIODevice *QAndroidMediaPlayer::mediaStream() const
{
    return mMediaStream;
}

void QAndroidMediaPlayer::setMedia(const QUrl &mediaContent,
                                          QIODevice *stream)
{
    StateChangeNotifier notifier(this);

    mReloadingMedia = (mMediaContent == mediaContent) && !mPendingSetMedia;

    if (!mReloadingMedia) {
        mMediaContent = mediaContent;
        mMediaStream = stream;
    }

    if (mediaContent.isEmpty()) {
        setMediaStatus(QMediaPlayer::NoMedia);
    } else {
        if (mVideoOutput && !mVideoOutput->isReady()) {
            // if a video output is set but the video texture is not ready, delay loading the media
            // since it can cause problems on some hardware
            mPendingSetMedia = true;
            return;
        }

        if (mVideoSize.isValid() && mVideoOutput)
            mVideoOutput->setVideoSize(mVideoSize);

        if ((mMediaPlayer->display() == 0) && mVideoOutput)
            mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture());
        mMediaPlayer->setDataSource(QNetworkRequest(mediaContent));
        mMediaPlayer->prepareAsync();

        if (!mReloadingMedia)
            setMediaStatus(QMediaPlayer::LoadingMedia);
    }

    resetBufferingProgress();

    mReloadingMedia = false;
}

void QAndroidMediaPlayer::setVideoSink(QVideoSink *sink)
{
    if (m_videoSink == sink)
        return;

    m_videoSink = sink;

    if (!m_videoSink) {
        return;
    }

    if (mVideoOutput) {
        delete mVideoOutput;
        mVideoOutput = nullptr;
        mMediaPlayer->setDisplay(nullptr);
    }

    mVideoOutput = new QAndroidTextureVideoOutput(this);
    connect(mVideoOutput, &QAndroidTextureVideoOutput::readyChanged, this,
            &QAndroidMediaPlayer::onVideoOutputReady);
    connect(mMediaPlayer, &AndroidMediaPlayer::timedTextChanged, mVideoOutput,
            &QAndroidTextureVideoOutput::setSubtitle);

    mVideoOutput->setSurface(sink);

    if (mVideoOutput->isReady())
        mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture());
}

void QAndroidMediaPlayer::setAudioOutput(QPlatformAudioOutput *output)
{
    if (m_audioOutput == output)
        return;
    if (m_audioOutput)
        m_audioOutput->q->disconnect(this);
    m_audioOutput = static_cast<QAndroidAudioOutput *>(output);
    if (m_audioOutput) {
        connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this, &QAndroidMediaPlayer::updateAudioDevice);
        connect(m_audioOutput->q, &QAudioOutput::volumeChanged, this, &QAndroidMediaPlayer::setVolume);
        connect(m_audioOutput->q, &QAudioOutput::mutedChanged, this, &QAndroidMediaPlayer::setMuted);
        updateAudioDevice();
    }
}

void QAndroidMediaPlayer::updateAudioDevice()
{
    if (m_audioOutput)
        mMediaPlayer->setAudioOutput(m_audioOutput->device.id());
}

void QAndroidMediaPlayer::play()
{
    StateChangeNotifier notifier(this);

    resetCurrentLoop();

    // We need to prepare the mediaplayer again.
    if ((mState & AndroidMediaPlayer::Stopped) && !mMediaContent.isEmpty()) {
        setMedia(mMediaContent, mMediaStream);
    }

    if (!mMediaContent.isEmpty())
        stateChanged(QMediaPlayer::PlayingState);

    if ((mState & (AndroidMediaPlayer::Prepared
                   | AndroidMediaPlayer::Started
                   | AndroidMediaPlayer::Paused
                   | AndroidMediaPlayer::PlaybackCompleted)) == 0) {
        mPendingState = QMediaPlayer::PlayingState;
        return;
    }

    if (mVideoOutput)
        mVideoOutput->start();

    updateAudioDevice();

    if (mHasPendingPlaybackRate) {
        mHasPendingPlaybackRate = false;
        if (mMediaPlayer->setPlaybackRate(mCurrentPlaybackRate))
            return;
        mCurrentPlaybackRate = mMediaPlayer->playbackRate();
        Q_EMIT playbackRateChanged(mCurrentPlaybackRate);
    }

    mMediaPlayer->play();
}

void QAndroidMediaPlayer::pause()
{
    // cannot pause without media
    if (mediaStatus() == QMediaPlayer::NoMedia)
        return;

    StateChangeNotifier notifier(this);

    stateChanged(QMediaPlayer::PausedState);

    if ((mState & (AndroidMediaPlayer::Started
                   | AndroidMediaPlayer::Paused
                   | AndroidMediaPlayer::PlaybackCompleted
                   | AndroidMediaPlayer::Prepared
                   | AndroidMediaPlayer::Stopped)) == 0) {
        mPendingState = QMediaPlayer::PausedState;
        return;
    }
    if (mVideoOutput)
        mVideoOutput->renderFrame();

    const qint64 currentPosition = mMediaPlayer->getCurrentPosition();
    setPosition(currentPosition);

    mMediaPlayer->pause();
}

void QAndroidMediaPlayer::stop()
{
    StateChangeNotifier notifier(this);

    stateChanged(QMediaPlayer::StoppedState);

    if ((mState & (AndroidMediaPlayer::Prepared
                   | AndroidMediaPlayer::Started
                   | AndroidMediaPlayer::Stopped
                   | AndroidMediaPlayer::Paused
                   | AndroidMediaPlayer::PlaybackCompleted)) == 0) {
        if ((mState & (AndroidMediaPlayer::Idle | AndroidMediaPlayer::Uninitialized | AndroidMediaPlayer::Error)) == 0)
            mPendingState = QMediaPlayer::StoppedState;
        return;
    }

    if (mCurrentPlaybackRate != 1.)
        // Playback rate need to by reapplied
        mHasPendingPlaybackRate = true;

    if (mVideoOutput)
        mVideoOutput->stop();

    mMediaPlayer->stop();
}

void QAndroidMediaPlayer::onInfo(qint32 what, qint32 extra)
{
    StateChangeNotifier notifier(this);

    Q_UNUSED(extra);
    switch (what) {
    case AndroidMediaPlayer::MEDIA_INFO_UNKNOWN:
        break;
    case AndroidMediaPlayer::MEDIA_INFO_VIDEO_TRACK_LAGGING:
        // IGNORE
        break;
    case AndroidMediaPlayer::MEDIA_INFO_VIDEO_RENDERING_START:
        break;
    case AndroidMediaPlayer::MEDIA_INFO_BUFFERING_START:
        mPendingState = state();
        stateChanged(QMediaPlayer::PausedState);
        setMediaStatus(QMediaPlayer::StalledMedia);
        break;
    case AndroidMediaPlayer::MEDIA_INFO_BUFFERING_END:
        if (state() != QMediaPlayer::StoppedState)
            flushPendingStates();
        break;
    case AndroidMediaPlayer::MEDIA_INFO_BAD_INTERLEAVING:
        break;
    case AndroidMediaPlayer::MEDIA_INFO_NOT_SEEKABLE:
        seekableChanged(false);
        break;
    case AndroidMediaPlayer::MEDIA_INFO_METADATA_UPDATE:
        Q_EMIT metaDataChanged();
        break;
    }
}

void QAndroidMediaPlayer::onError(qint32 what, qint32 extra)
{
    StateChangeNotifier notifier(this);

    QString errorString;
    QMediaPlayer::Error error = QMediaPlayer::ResourceError;

    switch (what) {
    case AndroidMediaPlayer::MEDIA_ERROR_UNKNOWN:
        errorString = QLatin1String("Error:");
        break;
    case AndroidMediaPlayer::MEDIA_ERROR_SERVER_DIED:
        errorString = QLatin1String("Error: Server died");
        error = QMediaPlayer::ResourceError;
        break;
    case AndroidMediaPlayer::MEDIA_ERROR_INVALID_STATE:
        errorString = QLatin1String("Error: Invalid state");
        error = QMediaPlayer::ResourceError;
        break;
    }

    switch (extra) {
    case AndroidMediaPlayer::MEDIA_ERROR_IO: // Network OR file error
        errorString += QLatin1String(" (I/O operation failed)");
        error = QMediaPlayer::NetworkError;
        setMediaStatus(QMediaPlayer::InvalidMedia);
        break;
    case AndroidMediaPlayer::MEDIA_ERROR_MALFORMED:
        errorString += QLatin1String(" (Malformed bitstream)");
        error = QMediaPlayer::FormatError;
        setMediaStatus(QMediaPlayer::InvalidMedia);
        break;
    case AndroidMediaPlayer::MEDIA_ERROR_UNSUPPORTED:
        errorString += QLatin1String(" (Unsupported media)");
        error = QMediaPlayer::FormatError;
        setMediaStatus(QMediaPlayer::InvalidMedia);
        break;
    case AndroidMediaPlayer::MEDIA_ERROR_TIMED_OUT:
        errorString += QLatin1String(" (Timed out)");
        break;
    case AndroidMediaPlayer::MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
        errorString += QLatin1String(" (Unable to start progressive playback')");
        error = QMediaPlayer::FormatError;
        setMediaStatus(QMediaPlayer::InvalidMedia);
        break;
    case AndroidMediaPlayer::MEDIA_ERROR_BAD_THINGS_ARE_GOING_TO_HAPPEN:
        errorString += mMediaContent.scheme() == QLatin1String("rtsp")
            ? QLatin1String(" (Unknown error/Insufficient resources or RTSP may not be supported)")
            : QLatin1String(" (Unknown error/Insufficient resources)");
        error = QMediaPlayer::ResourceError;
        break;
    }

    Q_EMIT QPlatformMediaPlayer::error(error, errorString);
}

void QAndroidMediaPlayer::onBufferingChanged(qint32 percent)
{
    StateChangeNotifier notifier(this);

    mBuffering = percent != 100;
    mBufferPercent = percent;

    updateAvailablePlaybackRanges();

    if (state() != QMediaPlayer::StoppedState)
        setMediaStatus(mBuffering ? QMediaPlayer::BufferingMedia : QMediaPlayer::BufferedMedia);

    updateBufferStatus();
}

void QAndroidMediaPlayer::onVideoSizeChanged(qint32 width, qint32 height)
{
    QSize newSize(width, height);

    if (width == 0 || height == 0 || newSize == mVideoSize)
        return;

    setVideoAvailable(true);
    mVideoSize = newSize;

    if (mVideoOutput)
        mVideoOutput->setVideoSize(mVideoSize);
}

void QAndroidMediaPlayer::onStateChanged(qint32 state)
{
    // If reloading, don't report state changes unless the new state is Prepared or Error.
    if ((mState & AndroidMediaPlayer::Stopped)
        && (state & (AndroidMediaPlayer::Prepared | AndroidMediaPlayer::Error | AndroidMediaPlayer::Uninitialized)) == 0) {
        return;
    }

    StateChangeNotifier notifier(this);

    mState = state;
    switch (mState) {
    case AndroidMediaPlayer::Idle:
        break;
    case AndroidMediaPlayer::Initialized:
        break;
    case AndroidMediaPlayer::Preparing:
        if (!mReloadingMedia)
            setMediaStatus(QMediaPlayer::LoadingMedia);
        break;
    case AndroidMediaPlayer::Prepared:
        setMediaStatus(QMediaPlayer::LoadedMedia);
        if (mBuffering) {
            setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
                                                 : QMediaPlayer::BufferingMedia);
        } else {
            onBufferingChanged(100);
        }
        setPosition(0);
        Q_EMIT metaDataChanged();
        setAudioAvailable(true);
        flushPendingStates();
        break;
    case AndroidMediaPlayer::Started:
        stateChanged(QMediaPlayer::PlayingState);
        if (mBuffering) {
            setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia
                                                 : QMediaPlayer::BufferingMedia);
        } else {
            setMediaStatus(QMediaPlayer::BufferedMedia);
        }
        Q_EMIT positionChanged(position());
        break;
    case AndroidMediaPlayer::Paused:
        stateChanged(QMediaPlayer::PausedState);
        if (mediaStatus() == QMediaPlayer::EndOfMedia) {
            setPosition(0);
            setMediaStatus(QMediaPlayer::BufferedMedia);
        } else {
            Q_EMIT positionChanged(position());
        }
        break;
    case AndroidMediaPlayer::Error:
        stateChanged(QMediaPlayer::StoppedState);
        setMediaStatus(QMediaPlayer::InvalidMedia);
        mMediaPlayer->release();
        Q_EMIT positionChanged(0);
        break;
    case AndroidMediaPlayer::Stopped:
        stateChanged(QMediaPlayer::StoppedState);
        setMediaStatus(QMediaPlayer::LoadedMedia);
        Q_EMIT positionChanged(0);
        break;
    case AndroidMediaPlayer::PlaybackCompleted:
        if (doLoop()) {
            setPosition(0);
            mMediaPlayer->play();
            break;
        }
        stateChanged(QMediaPlayer::StoppedState);
        setMediaStatus(QMediaPlayer::EndOfMedia);
        break;
    case AndroidMediaPlayer::Uninitialized:
        // reset some properties (unless we reload the same media)
        if (!mReloadingMedia) {
            resetBufferingProgress();
            mPendingPosition = -1;
            mPendingSetMedia = false;
            mPendingState = -1;

            Q_EMIT durationChanged(0);
            Q_EMIT positionChanged(0);

            setAudioAvailable(false);
            setVideoAvailable(false);
            seekableChanged(true);
        }
        break;
    default:
        break;
    }

    if ((mState & (AndroidMediaPlayer::Stopped | AndroidMediaPlayer::Uninitialized)) != 0) {
        mMediaPlayer->setDisplay(0);
        if (mVideoOutput) {
            mVideoOutput->stop();
            mVideoOutput->reset();
        }
    }
}

int QAndroidMediaPlayer::trackCount(TrackType trackType)
{
    if (!mTracksMetadata.contains(trackType))
        return -1;

    auto tracks = mTracksMetadata.value(trackType);
    return tracks.count();
}

QMediaMetaData QAndroidMediaPlayer::trackMetaData(TrackType trackType, int streamNumber)
{
    if (!mTracksMetadata.contains(trackType))
        return QMediaMetaData();

    auto tracks = mTracksMetadata.value(trackType);
    if (tracks.count() < streamNumber)
        return QMediaMetaData();

    QAndroidMetaData trackInfo = tracks.at(streamNumber);
    return static_cast<QMediaMetaData>(trackInfo);
}

QPlatformMediaPlayer::TrackType convertTrackType(AndroidMediaPlayer::TrackType type)
{
    switch (type) {
    case AndroidMediaPlayer::TrackType::Video:
        return QPlatformMediaPlayer::TrackType::VideoStream;
    case AndroidMediaPlayer::TrackType::Audio:
        return QPlatformMediaPlayer::TrackType::AudioStream;
    case AndroidMediaPlayer::TrackType::TimedText:
        return QPlatformMediaPlayer::TrackType::SubtitleStream;
    case AndroidMediaPlayer::TrackType::Subtitle:
        return QPlatformMediaPlayer::TrackType::SubtitleStream;
    case AndroidMediaPlayer::TrackType::Unknown:
    case AndroidMediaPlayer::TrackType::Metadata:
        return QPlatformMediaPlayer::TrackType::NTrackTypes;
    }

    return QPlatformMediaPlayer::TrackType::NTrackTypes;
}

int QAndroidMediaPlayer::convertTrackNumber(int androidTrackNumber)
{
    int trackNumber = androidTrackNumber;

    int videoTrackCount = trackCount(QPlatformMediaPlayer::TrackType::VideoStream);
    if (trackNumber <= videoTrackCount)
        return trackNumber;

    trackNumber = trackNumber - videoTrackCount;

    int audioTrackCount = trackCount(QPlatformMediaPlayer::TrackType::AudioStream);
    if (trackNumber <= audioTrackCount)
        return trackNumber;

    trackNumber = trackNumber - audioTrackCount;

    auto subtitleTracks = mTracksMetadata.value(QPlatformMediaPlayer::TrackType::SubtitleStream);
    int timedTextCount = 0;
    int subtitleTextCount = 0;
    for (const auto &track : subtitleTracks) {
        if (track.androidTrackType() == 3) // 3 == TimedText
            timedTextCount++;

        if (track.androidTrackType() == 4) // 4 == Subtitle
            subtitleTextCount++;
    }

    if (trackNumber <= timedTextCount)
        return trackNumber;

    trackNumber = trackNumber - timedTextCount;

    if (trackNumber <= subtitleTextCount)
        return trackNumber;

    return -1;
}

int QAndroidMediaPlayer::activeTrack(TrackType trackType)
{
    int androidTrackNumber = -1;

    switch (trackType) {
    case QPlatformMediaPlayer::TrackType::VideoStream: {
        if (!mIsVideoTrackEnabled)
            return -1;
        androidTrackNumber = mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Video);
    }
    case QPlatformMediaPlayer::TrackType::AudioStream: {
        if (!mIsAudioTrackEnabled)
            return -1;

        androidTrackNumber = mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Audio);
    }
    case QPlatformMediaPlayer::TrackType::SubtitleStream: {
        int timedTextSelectedTrack =
                mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::TimedText);

        if (timedTextSelectedTrack > -1) {
            androidTrackNumber = timedTextSelectedTrack;
            break;
        }

        int subtitleSelectedTrack =
                mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Subtitle);
        if (subtitleSelectedTrack > -1) {
            androidTrackNumber = subtitleSelectedTrack;
            break;
        }

        return -1;
    }
    case QPlatformMediaPlayer::TrackType::NTrackTypes:
        return -1;
    }

    return convertTrackNumber(androidTrackNumber);
}

void QAndroidMediaPlayer::disableTrack(TrackType trackType)
{
    const auto track = activeTrack(trackType);

    switch (trackType) {
    case VideoStream: {
        if (track > -1) {
            mMediaPlayer->setDisplay(nullptr);
            mIsVideoTrackEnabled = false;
        }
        break;
    }
    case AudioStream: {
        if (track > -1) {
            mMediaPlayer->setMuted(true);
            mMediaPlayer->blockAudio();
            mIsAudioTrackEnabled = false;
        }
        break;
    }
    case SubtitleStream: {
        // subtitles and timedtext tracks can be selected at the same time so deselect both
        int subtitleSelectedTrack =
                mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::Subtitle);
        if (subtitleSelectedTrack > -1)
            mMediaPlayer->deselectTrack(subtitleSelectedTrack);

        int timedTextSelectedTrack =
                mMediaPlayer->activeTrack(AndroidMediaPlayer::TrackType::TimedText);
        if (timedTextSelectedTrack > -1)
            mMediaPlayer->deselectTrack(timedTextSelectedTrack);

        break;
    }
    case NTrackTypes:
        break;
    }
}

void QAndroidMediaPlayer::setActiveTrack(TrackType trackType, int streamNumber)
{

    if (!mTracksMetadata.contains(trackType)) {
        qCWarning(lcMediaPlayer)
                << "Trying to set a active track which type has no available tracks.";
        return;
    }

    const auto &tracks = mTracksMetadata.value(trackType);
    if (streamNumber > tracks.count()) {
        qCWarning(lcMediaPlayer) << "Trying to set a active track that does not exist.";
        return;
    }

    // in case of < 0 deselect tracktype
    if (streamNumber < 0) {
        disableTrack(trackType);
        return;
    }

    const auto currentTrack = activeTrack(trackType);
    if (streamNumber == currentTrack) {
        return;
    }

    if (trackType == TrackType::VideoStream && !mIsVideoTrackEnabled) {
        // enable video stream
        mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture());
        mIsVideoTrackEnabled = true;
    }

    if (trackType == TrackType::AudioStream && !mIsAudioTrackEnabled) {
        // enable audio stream
        mMediaPlayer->unblockAudio();
        mMediaPlayer->setMuted(false);
        mIsAudioTrackEnabled = true;
    }

    if (trackType == TrackType::SubtitleStream) {
        // subtitles and timedtext tracks can be selected at the same time so deselect both before
        // selecting a new one
        disableTrack(TrackType::SubtitleStream);
    }

    const auto &trackInfo = tracks.at(streamNumber);
    const auto &trackNumber = trackInfo.androidTrackNumber();
    mMediaPlayer->selectTrack(trackNumber);

    emit activeTracksChanged();
}

void QAndroidMediaPlayer::positionChanged(qint64 position)
{
    QPlatformMediaPlayer::positionChanged(position);
}

void QAndroidMediaPlayer::durationChanged(qint64 duration)
{
    QPlatformMediaPlayer::durationChanged(duration);
}

void QAndroidMediaPlayer::onVideoOutputReady(bool ready)
{
    if ((mMediaPlayer->display() == 0) && mVideoOutput && ready)
        mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture());

    flushPendingStates();
}

void QAndroidMediaPlayer::setMediaStatus(QMediaPlayer::MediaStatus status)
{
    mediaStatusChanged(status);

    if (status == QMediaPlayer::NoMedia || status == QMediaPlayer::InvalidMedia) {
        Q_EMIT durationChanged(0);
        Q_EMIT metaDataChanged();
        setAudioAvailable(false);
        setVideoAvailable(false);
    }

    if (status == QMediaPlayer::EndOfMedia)
        Q_EMIT positionChanged(position());

    updateBufferStatus();
}

void QAndroidMediaPlayer::setAudioAvailable(bool available)
{
    if (mAudioAvailable == available)
        return;

    mAudioAvailable = available;
    Q_EMIT audioAvailableChanged(mAudioAvailable);
}

void QAndroidMediaPlayer::setVideoAvailable(bool available)
{
    if (mVideoAvailable == available)
        return;

    if (!available)
        mVideoSize = QSize();

    mVideoAvailable = available;
    Q_EMIT videoAvailableChanged(mVideoAvailable);
}

void QAndroidMediaPlayer::resetBufferingProgress()
{
    mBuffering = false;
    mBufferPercent = 0;
    mAvailablePlaybackRange = QMediaTimeRange();
}

void QAndroidMediaPlayer::flushPendingStates()
{
    if (mPendingSetMedia) {
        setMedia(mMediaContent, 0);
        mPendingSetMedia = false;
        return;
    }

    const int newState = mPendingState;
    mPendingState = -1;

    if (mPendingPosition != -1)
        setPosition(mPendingPosition);
    if (mPendingVolume >= 0)
        setVolume(mPendingVolume);
    if (mPendingMute != -1)
        setMuted((mPendingMute == 1));

    switch (newState) {
    case QMediaPlayer::PlayingState:
        play();
        break;
    case QMediaPlayer::PausedState:
        pause();
        break;
    case QMediaPlayer::StoppedState:
        stop();
        break;
    default:
        break;
    }
}

void QAndroidMediaPlayer::updateBufferStatus()
{
    const auto &status = mediaStatus();
    bool bufferFilled = (status == QMediaPlayer::BufferedMedia || status == QMediaPlayer::BufferingMedia);

    if (mBufferFilled != bufferFilled)
        mBufferFilled = bufferFilled;

    emit bufferProgressChanged(bufferProgress());
}

void QAndroidMediaPlayer::updateTrackInfo()
{
    const auto &androidTracksInfo = mMediaPlayer->tracksInfo();

    // prepare mTracksMetadata
    mTracksMetadata[TrackType::VideoStream] = QList<QAndroidMetaData>();
    mTracksMetadata[TrackType::AudioStream] = QList<QAndroidMetaData>();
    mTracksMetadata[TrackType::SubtitleStream] = QList<QAndroidMetaData>();
    mTracksMetadata[TrackType::NTrackTypes] = QList<QAndroidMetaData>();

    for (const auto &androidTrackInfo : androidTracksInfo) {

        const auto &mediaPlayerType = convertTrackType(androidTrackInfo.trackType);
        auto &tracks = mTracksMetadata[mediaPlayerType];

        const QAndroidMetaData metadata(mediaPlayerType, androidTrackInfo.trackType,
                                        androidTrackInfo.trackNumber, androidTrackInfo.mimeType,
                                        androidTrackInfo.language);
        tracks.append(metadata);
    }

    emit tracksChanged();
}

QT_END_NAMESPACE
