File indexing completed on 2024-04-28 11:43:47
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2014-2015 Martin Klapetek <mklapetek@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "notifybyaudio_phonon.h" 0009 #include "debug_p.h" 0010 0011 #include <QFile> 0012 #include <QString> 0013 #include <QUrl> 0014 0015 #include "knotification.h" 0016 #include "knotifyconfig.h" 0017 0018 #include <phonon/audiooutput.h> 0019 #include <phonon/mediaobject.h> 0020 #include <phonon/mediasource.h> 0021 0022 NotifyByAudio::NotifyByAudio(QObject *parent) 0023 : KNotificationPlugin(parent) 0024 , m_audioOutput(nullptr) 0025 { 0026 } 0027 0028 NotifyByAudio::~NotifyByAudio() 0029 { 0030 qDeleteAll(m_reusablePhonons); 0031 delete m_audioOutput; 0032 } 0033 0034 void NotifyByAudio::notify(KNotification *notification, KNotifyConfig *config) 0035 { 0036 if (!m_audioOutput) { 0037 m_audioOutput = new Phonon::AudioOutput(Phonon::NotificationCategory, this); 0038 } 0039 const QString soundFilename = config->readEntry(QStringLiteral("Sound")); 0040 if (soundFilename.isEmpty()) { 0041 qCWarning(LOG_KNOTIFICATIONS) << "Audio notification requested, but no sound file provided in notifyrc file, aborting audio notification"; 0042 0043 finish(notification); 0044 return; 0045 } 0046 0047 QUrl soundURL; 0048 const auto dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); 0049 for (const QString &dataLocation : dataLocations) { 0050 soundURL = QUrl::fromUserInput(soundFilename, dataLocation + QStringLiteral("/sounds"), QUrl::AssumeLocalFile); 0051 if (soundURL.isLocalFile() && QFile::exists(soundURL.toLocalFile())) { 0052 break; 0053 } else if (!soundURL.isLocalFile() && soundURL.isValid()) { 0054 break; 0055 } 0056 soundURL.clear(); 0057 } 0058 if (soundURL.isEmpty()) { 0059 qCWarning(LOG_KNOTIFICATIONS) << "Audio notification requested, but sound file from notifyrc file was not found, aborting audio notification"; 0060 finish(notification); 0061 return; 0062 } 0063 0064 Phonon::MediaObject *m; 0065 0066 if (m_reusablePhonons.isEmpty()) { 0067 m = new Phonon::MediaObject(this); 0068 connect(m, &Phonon::MediaObject::finished, this, &NotifyByAudio::onAudioFinished); 0069 connect(m, &Phonon::MediaObject::stateChanged, this, &NotifyByAudio::stateChanged); 0070 Phonon::createPath(m, m_audioOutput); 0071 } else { 0072 m = m_reusablePhonons.takeFirst(); 0073 } 0074 0075 m->setCurrentSource(soundURL); 0076 m->play(); 0077 0078 if (notification->flags() & KNotification::LoopSound) { 0079 // Enqueuing essentially prevents the subsystem pipeline from partial teardown 0080 // which is the most desired thing in terms of load and delay between loop cycles. 0081 // All of this is timing dependent, which is why we want at least one source queued; 0082 // in reality the shorter the source the more sources we want to be queued to prevent 0083 // the MO from running out of sources. 0084 // Point being that all phonon signals are forcefully queued (because qthread has problems detecting !pthread threads), 0085 // so when you get for example the aboutToFinish signal the MO might already have stopped playing. 0086 // 0087 // And so we queue it three times at least; doesn't cost anything and keeps us safe. 0088 0089 m->enqueue(soundURL); 0090 m->enqueue(soundURL); 0091 m->enqueue(soundURL); 0092 0093 connect(m, &Phonon::MediaObject::currentSourceChanged, this, &NotifyByAudio::onAudioSourceChanged); 0094 } 0095 0096 Q_ASSERT(!m_notifications.value(m)); 0097 m_notifications.insert(m, notification); 0098 } 0099 0100 void NotifyByAudio::stateChanged(Phonon::State newState, Phonon::State oldState) 0101 { 0102 qCDebug(LOG_KNOTIFICATIONS) << "Changing audio state from" << oldState << "to" << newState; 0103 } 0104 0105 void NotifyByAudio::close(KNotification *notification) 0106 { 0107 Phonon::MediaObject *m = m_notifications.key(notification); 0108 0109 if (!m) { 0110 return; 0111 } 0112 0113 m->stop(); 0114 finishNotification(notification, m); 0115 } 0116 0117 void NotifyByAudio::onAudioFinished() 0118 { 0119 Phonon::MediaObject *m = qobject_cast<Phonon::MediaObject *>(sender()); 0120 0121 if (!m) { 0122 return; 0123 } 0124 0125 KNotification *notification = m_notifications.value(m, nullptr); 0126 0127 if (!notification) { 0128 // This means that close was called already so there's nothing else to do. 0129 // Ideally we should not be getting here if close has already been called 0130 // since stopping a mediaobject means it won't emit finished() *BUT* 0131 // since the finished signal is a queued connection in phonon it can happen 0132 // that the playing had already finished and we just had not got the signal yet 0133 return; 0134 } 0135 0136 // if the sound is short enough, we can't guarantee new sounds are 0137 // enqueued before finished is emitted. 0138 // so to make sure we are looping restart it when the sound finished 0139 if (notification && (notification->flags() & KNotification::LoopSound)) { 0140 m->play(); 0141 return; 0142 } 0143 0144 finishNotification(notification, m); 0145 } 0146 0147 void NotifyByAudio::finishNotification(KNotification *notification, Phonon::MediaObject *m) 0148 { 0149 m_notifications.remove(m); 0150 0151 if (notification) { 0152 finish(notification); 0153 } 0154 0155 disconnect(m, &Phonon::MediaObject::currentSourceChanged, this, &NotifyByAudio::onAudioSourceChanged); 0156 0157 m_reusablePhonons.append(m); 0158 } 0159 0160 void NotifyByAudio::onAudioSourceChanged(const Phonon::MediaSource &source) 0161 { 0162 Phonon::MediaObject *m = qobject_cast<Phonon::MediaObject *>(sender()); 0163 0164 if (!m) { 0165 return; 0166 } 0167 0168 m->enqueue(source); 0169 } 0170 0171 #include "moc_notifybyaudio_phonon.cpp"