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 }