File indexing completed on 2025-01-05 04:29:50

0001 /**
0002  * SPDX-FileCopyrightText: 2022-2023 Bart De Vries <bart@mogwai.be>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005  */
0006 
0007 #include "qtmediabackend.h"
0008 #include "qtmediabackendlogging.h"
0009 
0010 #include <memory>
0011 
0012 #include <QAudio>
0013 #include <QAudioOutput>
0014 #include <QDir>
0015 #include <QFile>
0016 #include <QFileInfo>
0017 #include <QImage>
0018 #include <QMediaMetaData>
0019 #include <QStandardPaths>
0020 #include <QTemporaryDir>
0021 #include <QTimer>
0022 
0023 class QtMediaBackendPrivate
0024 {
0025 private:
0026     friend class QtMediaBackend;
0027 
0028     KMediaSession *m_KMediaSession = nullptr;
0029 
0030     QMediaPlayer m_player;
0031     QAudioOutput m_output;
0032 
0033     std::unique_ptr<QTemporaryDir> imageCacheDir = nullptr;
0034 
0035     KMediaSession::Error translateErrorEnum(QMediaPlayer::Error errorEnum);
0036     KMediaSession::MediaStatus translateMediaStatusEnum(QMediaPlayer::MediaStatus mediaEnum);
0037     KMediaSession::PlaybackState translatePlaybackStateEnum(QMediaPlayer::PlaybackState playbackStateEnum);
0038 
0039     void parseMetaData();
0040 };
0041 
0042 QtMediaBackend::QtMediaBackend(QObject *parent)
0043     : AbstractMediaBackend(parent)
0044     , d(std::make_unique<QtMediaBackendPrivate>())
0045 {
0046     qCDebug(QtMediaBackendLog) << "QtMediaBackend::QtMediaBackend";
0047 
0048     d->m_KMediaSession = static_cast<KMediaSession *>(parent);
0049 
0050     d->m_player.setAudioOutput(&d->m_output);
0051 
0052     // connect to QMediaPlayer signals and dispatch to AbstractMediaBackend
0053     // signals and add debug output
0054     connect(&d->m_output, &QAudioOutput::mutedChanged, this, &QtMediaBackend::playerMutedSignalChanges);
0055     connect(&d->m_output, &QAudioOutput::volumeChanged, this, &QtMediaBackend::playerVolumeSignalChanges);
0056     connect(&d->m_player, &QMediaPlayer::sourceChanged, this, &QtMediaBackend::playerSourceSignalChanges);
0057     connect(&d->m_player, &QMediaPlayer::playbackStateChanged, this, &QtMediaBackend::playerStateSignalChanges);
0058     connect(&d->m_player, QOverload<QMediaPlayer::Error, const QString &>::of(&QMediaPlayer::errorOccurred), this, &QtMediaBackend::playerErrorSignalChanges);
0059     connect(&d->m_player, &QMediaPlayer::metaDataChanged, this, &QtMediaBackend::playerMetaDataSignalChanges);
0060     connect(&d->m_player, &QMediaPlayer::mediaStatusChanged, this, &QtMediaBackend::mediaStatusSignalChanges);
0061     connect(&d->m_player, &QMediaPlayer::playbackRateChanged, this, &QtMediaBackend::playerPlaybackRateSignalChanges);
0062     connect(&d->m_player, &QMediaPlayer::durationChanged, this, &QtMediaBackend::playerDurationSignalChanges);
0063     connect(&d->m_player, &QMediaPlayer::positionChanged, this, &QtMediaBackend::playerPositionSignalChanges);
0064     connect(&d->m_player, &QMediaPlayer::seekableChanged, this, &QtMediaBackend::playerSeekableSignalChanges);
0065 }
0066 
0067 QtMediaBackend::~QtMediaBackend()
0068 {
0069     qCDebug(QtMediaBackendLog) << "QtMediaBackend::~QtMediaBackend";
0070     d->m_player.stop();
0071 }
0072 
0073 KMediaSession::MediaBackends QtMediaBackend::backend() const
0074 {
0075     qCDebug(QtMediaBackendLog) << "QtMediaBackend::backend()";
0076     return KMediaSession::MediaBackends::Qt;
0077 }
0078 
0079 bool QtMediaBackend::muted() const
0080 {
0081     qCDebug(QtMediaBackendLog) << "QtMediaBackend::muted()";
0082     return d->m_output.isMuted();
0083 }
0084 
0085 qreal QtMediaBackend::volume() const
0086 {
0087     qCDebug(QtMediaBackendLog) << "QtMediaBackend::volume()";
0088     qreal realVolume = static_cast<qreal>(d->m_output.volume());
0089     qreal userVolume = static_cast<qreal>(QAudio::convertVolume(realVolume, QAudio::LinearVolumeScale, QAudio::LogarithmicVolumeScale));
0090 
0091     return userVolume * 100.0;
0092 }
0093 
0094 QUrl QtMediaBackend::source() const
0095 {
0096     qCDebug(QtMediaBackendLog) << "QtMediaBackend::source()";
0097     return d->m_player.source();
0098 }
0099 
0100 KMediaSession::MediaStatus QtMediaBackend::mediaStatus() const
0101 {
0102     qCDebug(QtMediaBackendLog) << "QtMediaBackend::mediaStatus()";
0103     return d->translateMediaStatusEnum(d->m_player.mediaStatus());
0104 }
0105 
0106 KMediaSession::PlaybackState QtMediaBackend::playbackState() const
0107 {
0108     qCDebug(QtMediaBackendLog) << "QtMediaBackend::playbackState()";
0109     return d->translatePlaybackStateEnum(d->m_player.playbackState());
0110 }
0111 
0112 qreal QtMediaBackend::playbackRate() const
0113 {
0114     qCDebug(QtMediaBackendLog) << "QtMediaBackend::playbackRate()";
0115     return d->m_player.playbackRate();
0116 }
0117 
0118 KMediaSession::Error QtMediaBackend::error() const
0119 {
0120     qCDebug(QtMediaBackendLog) << "QtMediaBackend::error()";
0121     return d->translateErrorEnum(d->m_player.error());
0122 }
0123 
0124 qint64 QtMediaBackend::duration() const
0125 {
0126     qCDebug(QtMediaBackendLog) << "QtMediaBackend::duration()";
0127     return d->m_player.duration();
0128 }
0129 
0130 qint64 QtMediaBackend::position() const
0131 {
0132     qCDebug(QtMediaBackendLog) << "QtMediaBackend::position()";
0133     return d->m_player.position();
0134 }
0135 
0136 bool QtMediaBackend::seekable() const
0137 {
0138     qCDebug(QtMediaBackendLog) << "QtMediaBackend::seekable()";
0139     return d->m_player.isSeekable();
0140 }
0141 
0142 void QtMediaBackend::setMuted(bool muted)
0143 {
0144     qCDebug(QtMediaBackendLog) << "QtMediaBackend::setMuted(" << muted << ")";
0145     d->m_output.setMuted(muted);
0146 }
0147 
0148 void QtMediaBackend::setVolume(qreal volume)
0149 {
0150     qCDebug(QtMediaBackendLog) << "QtMediaBackend::setVolume(" << volume << ")";
0151 
0152     qreal realVolume = static_cast<qreal>(QAudio::convertVolume(volume / 100.0, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale));
0153     d->m_output.setVolume(realVolume);
0154 }
0155 
0156 void QtMediaBackend::setSource(const QUrl &source)
0157 {
0158     qCDebug(QtMediaBackendLog) << "QtMediaBackend::setSource(" << source << ")";
0159     d->m_player.setSource(source);
0160 }
0161 
0162 void QtMediaBackend::setPosition(qint64 position)
0163 {
0164     qCDebug(QtMediaBackendLog) << "QtMediaBackend::setPosition(" << position << ")";
0165     d->m_player.setPosition(position);
0166 }
0167 
0168 void QtMediaBackend::setPlaybackRate(qreal rate)
0169 {
0170     qCDebug(QtMediaBackendLog) << "QtMediaBackend::setPlaybackRate(" << rate << ")";
0171     d->m_player.setPlaybackRate(rate);
0172 }
0173 
0174 void QtMediaBackend::pause()
0175 {
0176     qCDebug(QtMediaBackendLog) << "QtMediaBackend::pause()";
0177     d->m_player.pause();
0178 }
0179 
0180 void QtMediaBackend::play()
0181 {
0182     qCDebug(QtMediaBackendLog) << "QtMediaBackend::play()";
0183     d->m_player.play();
0184 }
0185 
0186 void QtMediaBackend::stop()
0187 {
0188     qCDebug(QtMediaBackendLog) << "QtMediaBackend::stop()";
0189     d->m_player.stop();
0190 }
0191 
0192 void QtMediaBackend::playerMutedSignalChanges(bool muted)
0193 {
0194     QTimer::singleShot(0, this, [this, muted]() {
0195         qCDebug(QtMediaBackendLog) << "QtMediaBackend::mutedChanged(" << muted << ")";
0196         Q_EMIT mutedChanged(muted);
0197     });
0198 }
0199 
0200 void QtMediaBackend::playerVolumeSignalChanges(float volume)
0201 {
0202     qreal realVolume = static_cast<qreal>(volume);
0203     qreal userVolume = static_cast<qreal>(QAudio::convertVolume(realVolume, QAudio::LinearVolumeScale, QAudio::LogarithmicVolumeScale)) * 100.0;
0204     QTimer::singleShot(0, this, [this, userVolume]() {
0205         qCDebug(QtMediaBackendLog) << "QtMediaBackend::volumeChanged(" << userVolume << ")";
0206         Q_EMIT volumeChanged(userVolume);
0207     });
0208 }
0209 
0210 void QtMediaBackend::playerSourceSignalChanges(const QUrl &media)
0211 {
0212     QUrl source = media;
0213 
0214     QTimer::singleShot(0, this, [this, source]() {
0215         qCDebug(QtMediaBackendLog) << "QtMediaBackend::sourceChanged(" << source << ")";
0216         Q_EMIT sourceChanged(source);
0217     });
0218 }
0219 
0220 void QtMediaBackend::mediaStatusSignalChanges(const QMediaPlayer::MediaStatus &qtMediaStatus)
0221 {
0222     const KMediaSession::MediaStatus mediaStatus = d->translateMediaStatusEnum(qtMediaStatus);
0223     QTimer::singleShot(0, this, [this, mediaStatus]() {
0224         Q_EMIT mediaStatusChanged(mediaStatus);
0225     });
0226 }
0227 
0228 void QtMediaBackend::playerStateSignalChanges(const QMediaPlayer::PlaybackState &qtPlaybackState)
0229 {
0230     const KMediaSession::PlaybackState playbackState = d->translatePlaybackStateEnum(qtPlaybackState);
0231     QTimer::singleShot(0, this, [this, playbackState]() {
0232         Q_EMIT playbackStateChanged(playbackState);
0233     });
0234 }
0235 
0236 void QtMediaBackend::playerPlaybackRateSignalChanges(const qreal &playbackRate)
0237 {
0238     QTimer::singleShot(0, this, [this, playbackRate]() {
0239         Q_EMIT playbackRateChanged(playbackRate);
0240     });
0241 }
0242 
0243 void QtMediaBackend::playerErrorSignalChanges(const QMediaPlayer::Error &error)
0244 {
0245     QTimer::singleShot(0, this, [this, error]() {
0246         Q_EMIT errorChanged(d->translateErrorEnum(error));
0247     });
0248 }
0249 
0250 void QtMediaBackend::playerDurationSignalChanges(qint64 newDuration)
0251 {
0252     QTimer::singleShot(0, this, [this, newDuration]() {
0253         qCDebug(QtMediaBackendLog) << "QtMediaBackend::durationChanged(" << newDuration << ")";
0254         Q_EMIT durationChanged(newDuration);
0255     });
0256 }
0257 
0258 void QtMediaBackend::playerPositionSignalChanges(qint64 newPosition)
0259 {
0260     QTimer::singleShot(0, this, [this, newPosition]() {
0261         qCDebug(QtMediaBackendLog) << "QtMediaBackend::positionChanged(" << newPosition << ")";
0262         Q_EMIT positionChanged(newPosition);
0263     });
0264 }
0265 
0266 void QtMediaBackend::playerSeekableSignalChanges(bool seekable)
0267 {
0268     QTimer::singleShot(0, this, [this, seekable]() {
0269         Q_EMIT seekableChanged(seekable);
0270     });
0271 }
0272 
0273 void QtMediaBackend::playerMetaDataSignalChanges()
0274 {
0275     d->parseMetaData();
0276 }
0277 
0278 KMediaSession::Error QtMediaBackendPrivate::translateErrorEnum(QMediaPlayer::Error errorEnum)
0279 {
0280     qCDebug(QtMediaBackendLog) << "QtMediaBackendPrivate::translateErrorEnum(" << errorEnum << ")";
0281     switch (errorEnum) {
0282     case QMediaPlayer::Error::NoError:
0283         return KMediaSession::Error::NoError;
0284     case QMediaPlayer::Error::ResourceError:
0285         return KMediaSession::Error::ResourceError;
0286     case QMediaPlayer::Error::FormatError:
0287         return KMediaSession::Error::FormatError;
0288     case QMediaPlayer::Error::NetworkError:
0289         return KMediaSession::Error::NetworkError;
0290     case QMediaPlayer::Error::AccessDeniedError:
0291         return KMediaSession::Error::AccessDeniedError;
0292     default:
0293         return KMediaSession::Error::NoError;
0294     }
0295 }
0296 
0297 KMediaSession::MediaStatus QtMediaBackendPrivate::translateMediaStatusEnum(QMediaPlayer::MediaStatus mediaEnum)
0298 {
0299     qCDebug(QtMediaBackendLog) << "QtMediaBackendPrivate::translateMediaStatusEnum(" << mediaEnum << ")";
0300     switch (mediaEnum) {
0301     case QMediaPlayer::MediaStatus::NoMedia:
0302         return KMediaSession::MediaStatus::NoMedia;
0303     case QMediaPlayer::MediaStatus::LoadingMedia:
0304         return KMediaSession::MediaStatus::LoadingMedia;
0305     case QMediaPlayer::MediaStatus::LoadedMedia:
0306         return KMediaSession::MediaStatus::LoadedMedia;
0307     case QMediaPlayer::MediaStatus::StalledMedia:
0308         return KMediaSession::MediaStatus::StalledMedia;
0309     case QMediaPlayer::MediaStatus::BufferingMedia:
0310         return KMediaSession::MediaStatus::BufferingMedia;
0311     case QMediaPlayer::MediaStatus::BufferedMedia:
0312         return KMediaSession::MediaStatus::BufferedMedia;
0313     case QMediaPlayer::MediaStatus::EndOfMedia:
0314         return KMediaSession::MediaStatus::EndOfMedia;
0315     case QMediaPlayer::MediaStatus::InvalidMedia:
0316         return KMediaSession::MediaStatus::InvalidMedia;
0317     default:
0318         return KMediaSession::MediaStatus::NoMedia;
0319     }
0320 }
0321 
0322 KMediaSession::PlaybackState QtMediaBackendPrivate::translatePlaybackStateEnum(QMediaPlayer::PlaybackState playbackStateEnum)
0323 {
0324     qCDebug(QtMediaBackendLog) << "QtMediaBackendPrivate::translateMediaStatusEnum(" << playbackStateEnum << ")";
0325 
0326     switch (playbackStateEnum) {
0327     case QMediaPlayer::PlaybackState::StoppedState:
0328         return KMediaSession::PlaybackState::StoppedState;
0329     case QMediaPlayer::PlaybackState::PlayingState:
0330         return KMediaSession::PlaybackState::PlayingState;
0331     case QMediaPlayer::PlaybackState::PausedState:
0332         return KMediaSession::PlaybackState::PausedState;
0333     default:
0334         return KMediaSession::PlaybackState::StoppedState;
0335     }
0336 }
0337 
0338 void QtMediaBackendPrivate::parseMetaData()
0339 {
0340     qCDebug(QtMediaBackendLog) << "QtMediaBackendPrivate::parseMetaData()";
0341 
0342     if (m_KMediaSession->metaData()->title().isEmpty()) {
0343         m_KMediaSession->metaData()->setTitle(m_player.metaData().stringValue(QMediaMetaData::Title));
0344     }
0345 
0346     if (m_KMediaSession->metaData()->artist().isEmpty()) {
0347         m_KMediaSession->metaData()->setArtist(m_player.metaData().stringValue(QMediaMetaData::ContributingArtist));
0348     }
0349 
0350     if (m_KMediaSession->metaData()->album().isEmpty()) {
0351         m_KMediaSession->metaData()->setAlbum(m_player.metaData().stringValue(QMediaMetaData::AlbumTitle));
0352     }
0353     if (m_KMediaSession->metaData()->artworkUrl().isEmpty()) {
0354         if (m_player.metaData().value(QMediaMetaData::CoverArtImage).isValid()) {
0355             imageCacheDir = std::make_unique<QTemporaryDir>();
0356             if (imageCacheDir->isValid()) {
0357                 QString filePath = imageCacheDir->path() + QStringLiteral("/coverimage");
0358 
0359                 bool success = m_player.metaData().value(QMediaMetaData::CoverArtImage).value<QImage>().save(filePath, "PNG");
0360 
0361                 if (success) {
0362                     QString localFilePath = QStringLiteral("file://") + filePath;
0363                     m_KMediaSession->metaData()->setArtworkUrl(QUrl(localFilePath));
0364                 }
0365             }
0366         }
0367     }
0368 }