/* SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien SPDX-License-Identifier: LGPL-3.0-or-later */ #include "manageaudioplayer.h" #include "mediaplaylist.h" #include "elisa_settings.h" #include "playerLogging.h" #include #include ManageAudioPlayer::ManageAudioPlayer(QObject *parent) : QObject(parent) { } QPersistentModelIndex ManageAudioPlayer::currentTrack() const { return mCurrentTrack; } QAbstractItemModel *ManageAudioPlayer::playListModel() const { return mPlayListModel; } int ManageAudioPlayer::urlRole() const { return mUrlRole; } int ManageAudioPlayer::isPlayingRole() const { return mIsPlayingRole; } QUrl ManageAudioPlayer::playerSource() const { if (!mCurrentTrack.isValid()) { return QUrl(); } return mCurrentTrack.data(mUrlRole).toUrl(); } QMediaPlayer::MediaStatus ManageAudioPlayer::playerStatus() const { return mPlayerStatus; } QMediaPlayer::PlaybackState ManageAudioPlayer::playerPlaybackState() const { return mPlayerPlaybackState; } QMediaPlayer::Error ManageAudioPlayer::playerError() const { return mPlayerError; } qint64 ManageAudioPlayer::audioDuration() const { return mAudioDuration; } bool ManageAudioPlayer::playerIsSeekable() const { return mPlayerIsSeekable; } qint64 ManageAudioPlayer::playerPosition() const { return mPlayerPosition; } qint64 ManageAudioPlayer::playControlPosition() const { return mPlayerPosition; } QVariantMap ManageAudioPlayer::persistentState() const { auto persistentStateValue = QVariantMap(); persistentStateValue[QStringLiteral("isPlaying")] = mPlayingState; persistentStateValue[QStringLiteral("playerPosition")] = mPlayerPosition; persistentStateValue[QStringLiteral("playerDuration")] = mAudioDuration; if (mCurrentTrack.isValid()) { persistentStateValue[QStringLiteral("audioPlayerCurrentTitle")] = mCurrentTrack.data(mTitleRole); persistentStateValue[QStringLiteral("audioPlayerCurrentArtistName")] = mCurrentTrack.data(mArtistNameRole); persistentStateValue[QStringLiteral("audioPlayerCurrentAlbumName")] = mCurrentTrack.data(mAlbumNameRole); } else { persistentStateValue[QStringLiteral("audioPlayerCurrentTitle")] = {}; persistentStateValue[QStringLiteral("audioPlayerCurrentArtistName")] = {}; persistentStateValue[QStringLiteral("audioPlayerCurrentAlbumName")] = {}; } return persistentStateValue; } int ManageAudioPlayer::playListPosition() const { if (mCurrentTrack.isValid()) { return mCurrentTrack.row(); } return 0; } int ManageAudioPlayer::titleRole() const { return mTitleRole; } int ManageAudioPlayer::artistNameRole() const { return mArtistNameRole; } int ManageAudioPlayer::albumNameRole() const { return mAlbumNameRole; } void ManageAudioPlayer::setCurrentTrack(const QPersistentModelIndex ¤tTrack) { mOldCurrentTrack = mCurrentTrack; mCurrentTrack = currentTrack; if (mCurrentTrack.isValid()) { restorePreviousState(); } mPlayerError = QMediaPlayer::NoError; if (mOldCurrentTrack != mCurrentTrack || mPlayingState) { Q_EMIT currentTrackChanged(); } switch (mPlayerPlaybackState) { case QMediaPlayer::StoppedState: Q_EMIT playerSourceChanged(mCurrentTrack.data(mUrlRole).toUrl()); break; case QMediaPlayer::PlayingState: case QMediaPlayer::PausedState: triggerStop(); if (mPlayingState && !mCurrentTrack.isValid()) { mPlayingState = false; } mSkippingCurrentTrack = true; break; } } void ManageAudioPlayer::saveForUndoClearPlaylist(){ mUndoPlayingState = mPlayingState; mUndoPlayerPosition = mPlayerPosition; Q_EMIT saveUndoPositionInAudioWrapper(mUndoPlayerPosition); } void ManageAudioPlayer::restoreForUndoClearPlaylist(){ mPlayerPosition = mUndoPlayerPosition; Q_EMIT seek(mPlayerPosition); mPlayingState = mUndoPlayingState; Q_EMIT restoreUndoPositionInAudioWrapper(); } void ManageAudioPlayer::setPlayListModel(QAbstractItemModel *aPlayListModel) { if (mPlayListModel == aPlayListModel) { return; } if (mPlayListModel) { disconnect(mPlayListModel, &QAbstractItemModel::dataChanged, this, &ManageAudioPlayer::tracksDataChanged); } mPlayListModel = aPlayListModel; if (mPlayListModel) { connect(mPlayListModel, &QAbstractItemModel::dataChanged, this, &ManageAudioPlayer::tracksDataChanged); } Q_EMIT playListModelChanged(); } void ManageAudioPlayer::setUrlRole(int value) { mUrlRole = value; Q_EMIT urlRoleChanged(); notifyPlayerSourceProperty(); restorePreviousState(); } void ManageAudioPlayer::setIsPlayingRole(int value) { if (mIsPlayingRole == value) { return; } mIsPlayingRole = value; Q_EMIT isPlayingRoleChanged(); } void ManageAudioPlayer::setPlayerStatus(QMediaPlayer::MediaStatus playerStatus) { if (mPlayerStatus == playerStatus) { return; } mPlayerStatus = playerStatus; Q_EMIT playerStatusChanged(); switch (mPlayerStatus) { case QMediaPlayer::NoMedia: break; case QMediaPlayer::LoadingMedia: break; case QMediaPlayer::LoadedMedia: if (mPlayingState) { triggerPlay(); } break; case QMediaPlayer::BufferingMedia: break; case QMediaPlayer::StalledMedia: break; case QMediaPlayer::BufferedMedia: break; case QMediaPlayer::EndOfMedia: break; case QMediaPlayer::InvalidMedia: triggerSkipNextTrack(); break; } } void ManageAudioPlayer::setPlayerPlaybackState(QMediaPlayer::PlaybackState playerPlaybackState) { if (mPlayerPlaybackState == playerPlaybackState) { return; } mPlayerPlaybackState = playerPlaybackState; Q_EMIT playerPlaybackStateChanged(); if (!mSkippingCurrentTrack) { switch(mPlayerPlaybackState) { case QMediaPlayer::StoppedState: if (mPlayerStatus == QMediaPlayer::EndOfMedia) { Q_EMIT finishedPlayingTrack(mCurrentTrack.data(mUrlRole).toUrl(), QDateTime::currentDateTime()); } if (mPlayerStatus == QMediaPlayer::EndOfMedia || mPlayerStatus == QMediaPlayer::InvalidMedia) { triggerSkipNextTrack(); } if (mPlayListModel && mCurrentTrack.isValid()) { mPlayListModel->setData(mCurrentTrack, MediaPlayList::NotPlaying, mIsPlayingRole); } break; case QMediaPlayer::PlayingState: if (mPlayListModel && mCurrentTrack.isValid()) { mPlayListModel->setData(mCurrentTrack, MediaPlayList::IsPlaying, mIsPlayingRole); Q_EMIT startedPlayingTrack(mCurrentTrack.data(mUrlRole).toUrl(), QDateTime::currentDateTime()); } break; case QMediaPlayer::PausedState: if (mPlayListModel && mCurrentTrack.isValid()) { mPlayListModel->setData(mCurrentTrack, MediaPlayList::IsPaused, mIsPlayingRole); } break; } } else { switch(mPlayerPlaybackState) { case QMediaPlayer::StoppedState: notifyPlayerSourceProperty(); mSkippingCurrentTrack = false; if (mPlayListModel && mOldCurrentTrack.isValid()) { mPlayListModel->setData(mOldCurrentTrack, MediaPlayList::NotPlaying, mIsPlayingRole); } break; case QMediaPlayer::PlayingState: if (mPlayListModel && mCurrentTrack.isValid()) { mPlayListModel->setData(mCurrentTrack, MediaPlayList::IsPlaying, mIsPlayingRole); Q_EMIT startedPlayingTrack(mCurrentTrack.data(mUrlRole).toUrl(), QDateTime::currentDateTime()); } break; case QMediaPlayer::PausedState: if (mPlayListModel && mCurrentTrack.isValid()) { mPlayListModel->setData(mCurrentTrack, MediaPlayList::IsPaused, mIsPlayingRole); } break; } } } void ManageAudioPlayer::setPlayerError(QMediaPlayer::Error playerError) { if (mPlayerError == playerError) { return; } mPlayerError = playerError; Q_EMIT playerErrorChanged(); if (mPlayerError != QMediaPlayer::NoError) { auto currentSource = playerSource(); Q_EMIT sourceInError(currentSource, mPlayerError); if (currentSource.isLocalFile()) { Q_EMIT displayTrackError(currentSource.toLocalFile()); } else { Q_EMIT displayTrackError(currentSource.toString()); } } } void ManageAudioPlayer::ensurePause() { if (mPlayingState) { mPlayingState = false; triggerPause(); } } void ManageAudioPlayer::ensurePlay() { if (!mPlayingState) { mPlayingState = true; triggerPlay(); } } void ManageAudioPlayer::requestPlay() { mPlayingState = true; } void ManageAudioPlayer::stop() { mPlayingState = false; triggerStop(); } void ManageAudioPlayer::playPause() { mPlayingState = !mPlayingState; switch (mPlayerStatus) { case QMediaPlayer::LoadedMedia: case QMediaPlayer::BufferingMedia: case QMediaPlayer::BufferedMedia: case QMediaPlayer::LoadingMedia: if (mPlayingState) { triggerPlay(); } else { triggerPause(); } break; case QMediaPlayer::EndOfMedia: if (mPlayerPlaybackState == QMediaPlayer::PlayingState && !mPlayingState) { triggerPause(); } else if (mPlayerPlaybackState == QMediaPlayer::PausedState && mPlayingState) { triggerPlay(); } break; case QMediaPlayer::NoMedia: case QMediaPlayer::StalledMedia: case QMediaPlayer::InvalidMedia: break; } } void ManageAudioPlayer::setAudioDuration(qint64 audioDuration) { if (mAudioDuration == audioDuration) { return; } mAudioDuration = audioDuration; Q_EMIT audioDurationChanged(); } void ManageAudioPlayer::setPlayerIsSeekable(bool playerIsSeekable) { if (mPlayerIsSeekable == playerIsSeekable) { return; } mPlayerIsSeekable = playerIsSeekable; Q_EMIT playerIsSeekableChanged(); } void ManageAudioPlayer::setPlayerPosition(qint64 playerPosition) { if (mPlayerPosition == playerPosition) { return; } mPlayerPosition = playerPosition; Q_EMIT playerPositionChanged(); QTimer::singleShot(0, this, [this]() {Q_EMIT playControlPositionChanged();}); } void ManageAudioPlayer::setCurrentPlayingForRadios(const QString &title, const QString &artistOrStation) { if (mPlayListModel && mCurrentTrack.isValid()) { // WORKAROUND: Don't update metadata if the title is the last subpath of the stream URL. This is an unresolved issue with libVLC, // see issue 21057 here: https://code.videolan.org/videolan/vlc/-/issues/21057 bool suppressMetadataUpdate = !title.isEmpty() && mCurrentTrack.data(mUrlRole).toString().endsWith(title); if (!suppressMetadataUpdate) { Q_EMIT updateData(mCurrentTrack, title, MediaPlayList::TitleRole); Q_EMIT updateData(mCurrentTrack, artistOrStation, MediaPlayList::ArtistRole); } } } void ManageAudioPlayer::setPlayControlPosition(int playerPosition) { qCDebug(orgKdeElisaPlayer()) << "ManageAudioPlayer::setPlayControlPosition" << playerPosition; Q_EMIT seek(playerPosition); } void ManageAudioPlayer::setPersistentState(const QVariantMap &persistentStateValue) { if (mPersistentState == persistentStateValue) { return; } mPersistentState = persistentStateValue; Q_EMIT persistentStateChanged(); if (mCurrentTrack.isValid()) { restorePreviousState(); } } void ManageAudioPlayer::playerSeek(int position) { qCDebug(orgKdeElisaPlayer()) << "ManageAudioPlayer::playerSeek" << position; Q_EMIT seek(position); } void ManageAudioPlayer::playListFinished() { mPlayingState = false; } void ManageAudioPlayer::tracksDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList &roles) { if (!mCurrentTrack.isValid()) { return; } if (mCurrentTrack.row() > bottomRight.row() || mCurrentTrack.row() < topLeft.row()) { return; } if (mCurrentTrack.column() > bottomRight.column() || mCurrentTrack.column() < topLeft.column()) { return; } if (roles.isEmpty()) { notifyPlayerSourceProperty(); restorePreviousState(); } else { for(auto oneRole : roles) { if (oneRole == mUrlRole) { notifyPlayerSourceProperty(); restorePreviousState(); } } } } void ManageAudioPlayer::setTitleRole(int titleRole) { if (mTitleRole == titleRole) { return; } mTitleRole = titleRole; Q_EMIT titleRoleChanged(); if (mCurrentTrack.isValid()) { restorePreviousState(); } } void ManageAudioPlayer::setArtistNameRole(int artistNameRole) { if (mArtistNameRole == artistNameRole) { return; } mArtistNameRole = artistNameRole; Q_EMIT artistNameRoleChanged(); if (mCurrentTrack.isValid()) { restorePreviousState(); } } void ManageAudioPlayer::setAlbumNameRole(int albumNameRole) { if (mAlbumNameRole == albumNameRole) { return; } mAlbumNameRole = albumNameRole; Q_EMIT albumNameRoleChanged(); if (mCurrentTrack.isValid()) { restorePreviousState(); } } void ManageAudioPlayer::notifyPlayerSourceProperty() { auto newUrlValue = mCurrentTrack.data(mUrlRole); if (mSkippingCurrentTrack || mOldPlayerSource != newUrlValue) { Q_EMIT playerSourceChanged(mCurrentTrack.data(mUrlRole).toUrl()); mOldPlayerSource = newUrlValue; } } void ManageAudioPlayer::triggerPlay() { QTimer::singleShot(0, this, [this]() {Q_EMIT playerPlay();}); } void ManageAudioPlayer::triggerPause() { QTimer::singleShot(0, this, [this]() {Q_EMIT playerPause();}); } void ManageAudioPlayer::triggerStop() { QTimer::singleShot(0, this, [this]() {Q_EMIT playerStop();}); } void ManageAudioPlayer::triggerSkipNextTrack(ElisaUtils::SkipReason reason /*= SkipReason::Automatic */) { QTimer::singleShot(0, this, [this, reason]() {Q_EMIT skipNextTrack(reason);}); } void ManageAudioPlayer::restorePreviousState() { if (mPersistentState.isEmpty()) { return; } auto itTitle = mPersistentState.find(QStringLiteral("audioPlayerCurrentTitle")); auto itArtistName = mPersistentState.find(QStringLiteral("audioPlayerCurrentArtistName")); auto itAlbumName = mPersistentState.find(QStringLiteral("audioPlayerCurrentAlbumName")); if (itTitle == mPersistentState.end() || itArtistName == mPersistentState.end() || itAlbumName == mPersistentState.end()) { return; } if (*itTitle != mCurrentTrack.data(mTitleRole) || (itArtistName->isValid() && *itArtistName != mCurrentTrack.data(mArtistNameRole)) || (itAlbumName->isValid() && *itAlbumName != mCurrentTrack.data(mAlbumNameRole))) { if (mCurrentTrack.isValid() && mCurrentTrack.data(mTitleRole).isValid() && mCurrentTrack.data(mArtistNameRole).isValid() && mCurrentTrack.data(mAlbumNameRole).isValid()) { mPersistentState.clear(); } return; } if (!mCurrentTrack.data(mUrlRole).toUrl().isValid()) { return; } auto isPlaying = mPersistentState.find(QStringLiteral("isPlaying")); auto currentConfiguration = Elisa::ElisaConfiguration::self(); bool autoPlay = currentConfiguration->playAtStartup(); if (autoPlay && isPlaying != mPersistentState.end() && mPlayingState != isPlaying->toBool()) { mPlayingState = isPlaying->toBool(); } auto playerPosition = mPersistentState.find(QStringLiteral("playerPosition")); if (playerPosition != mPersistentState.end()) { setPlayerPosition(playerPosition->toLongLong()); Q_EMIT seek(mPlayerPosition); } auto playerDuration = mPersistentState.find(QStringLiteral("playerDuration")); if (playerDuration != mPersistentState.end()) { setAudioDuration(playerDuration->toInt()); } mPersistentState.clear(); } #include "moc_manageaudioplayer.cpp"