File indexing completed on 2024-04-28 04:32:37
0001 /* 0002 SPDX-FileCopyrightText: 2007 Pino Toscano <pino@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "audioplayer.h" 0008 0009 // qt/kde includes 0010 #include <KLocalizedString> 0011 #include <QBuffer> 0012 #include <QDebug> 0013 #include <QDir> 0014 #include <QRandomGenerator> 0015 0016 #include "config-okular.h" 0017 0018 #if HAVE_PHONON 0019 #include <phonon/abstractmediastream.h> 0020 #include <phonon/audiooutput.h> 0021 #include <phonon/mediaobject.h> 0022 #include <phonon/path.h> 0023 #endif 0024 0025 // local includes 0026 #include "action.h" 0027 #include "debug_p.h" 0028 #include "document.h" 0029 #include "sound.h" 0030 #include <stdlib.h> 0031 0032 using namespace Okular; 0033 0034 #if HAVE_PHONON 0035 0036 class PlayData; 0037 class SoundInfo; 0038 0039 namespace Okular 0040 { 0041 class AudioPlayerPrivate 0042 { 0043 public: 0044 explicit AudioPlayerPrivate(AudioPlayer *qq); 0045 0046 ~AudioPlayerPrivate(); 0047 0048 int newId() const; 0049 bool play(const SoundInfo &si); 0050 void stopPlayings(); 0051 0052 void finished(int); 0053 0054 AudioPlayer *q; 0055 0056 QHash<int, PlayData *> m_playing; 0057 QUrl m_currentDocument; 0058 AudioPlayer::State m_state; 0059 }; 0060 } 0061 0062 // helper class used to store info about a sound to be played 0063 class SoundInfo 0064 { 0065 public: 0066 explicit SoundInfo(const Sound *s = nullptr, const SoundAction *ls = nullptr) 0067 : sound(s) 0068 , volume(0.5) 0069 , synchronous(false) 0070 , repeat(false) 0071 , mix(false) 0072 { 0073 if (ls) { 0074 volume = ls->volume(); 0075 synchronous = ls->synchronous(); 0076 repeat = ls->repeat(); 0077 mix = ls->mix(); 0078 } 0079 } 0080 0081 const Sound *sound; 0082 double volume; 0083 bool synchronous; 0084 bool repeat; 0085 bool mix; 0086 }; 0087 0088 class PlayData 0089 { 0090 public: 0091 PlayData() 0092 : m_mediaobject(nullptr) 0093 , m_output(nullptr) 0094 , m_buffer(nullptr) 0095 { 0096 } 0097 0098 void play() 0099 { 0100 if (m_buffer) { 0101 m_buffer->open(QIODevice::ReadOnly); 0102 } 0103 m_mediaobject->play(); 0104 } 0105 0106 ~PlayData() 0107 { 0108 m_mediaobject->stop(); 0109 delete m_mediaobject; 0110 delete m_output; 0111 delete m_buffer; 0112 } 0113 0114 PlayData(const PlayData &) = delete; 0115 PlayData &operator=(const PlayData &) = delete; 0116 0117 Phonon::MediaObject *m_mediaobject; 0118 Phonon::AudioOutput *m_output; 0119 QBuffer *m_buffer; 0120 SoundInfo m_info; 0121 }; 0122 0123 AudioPlayerPrivate::AudioPlayerPrivate(AudioPlayer *qq) 0124 : q(qq) 0125 , m_state(AudioPlayer::StoppedState) 0126 { 0127 } 0128 0129 AudioPlayerPrivate::~AudioPlayerPrivate() 0130 { 0131 stopPlayings(); 0132 } 0133 0134 int AudioPlayerPrivate::newId() const 0135 { 0136 auto random = QRandomGenerator::global(); 0137 int newid = 0; 0138 QHash<int, PlayData *>::const_iterator it; 0139 QHash<int, PlayData *>::const_iterator itEnd = m_playing.constEnd(); 0140 do { 0141 newid = random->bounded(RAND_MAX); 0142 it = m_playing.constFind(newid); 0143 } while (it != itEnd); 0144 return newid; 0145 } 0146 0147 bool AudioPlayerPrivate::play(const SoundInfo &si) 0148 { 0149 qCDebug(OkularCoreDebug); 0150 PlayData *data = new PlayData(); 0151 data->m_output = new Phonon::AudioOutput(Phonon::NotificationCategory); 0152 data->m_output->setVolume(si.volume); 0153 data->m_mediaobject = new Phonon::MediaObject(); 0154 Phonon::createPath(data->m_mediaobject, data->m_output); 0155 data->m_info = si; 0156 bool valid = false; 0157 0158 switch (si.sound->soundType()) { 0159 case Sound::External: { 0160 QString url = si.sound->url(); 0161 qCDebug(OkularCoreDebug) << "External," << url; 0162 if (!url.isEmpty()) { 0163 int newid = newId(); 0164 QObject::connect(data->m_mediaobject, &Phonon::MediaObject::finished, q, [this, newid]() { finished(newid); }); 0165 const QUrl newurl = QUrl::fromUserInput(url, m_currentDocument.adjusted(QUrl::RemoveFilename).toLocalFile()); 0166 data->m_mediaobject->setCurrentSource(newurl); 0167 m_playing.insert(newid, data); 0168 valid = true; 0169 } 0170 break; 0171 } 0172 case Sound::Embedded: { 0173 QByteArray filedata = si.sound->data(); 0174 qCDebug(OkularCoreDebug) << "Embedded," << filedata.length(); 0175 if (!filedata.isEmpty()) { 0176 qCDebug(OkularCoreDebug) << "Mediaobject:" << data->m_mediaobject; 0177 int newid = newId(); 0178 QObject::connect(data->m_mediaobject, &Phonon::MediaObject::finished, q, [this, newid]() { finished(newid); }); 0179 data->m_buffer = new QBuffer(); 0180 data->m_buffer->setData(filedata); 0181 data->m_mediaobject->setCurrentSource(Phonon::MediaSource(data->m_buffer)); 0182 m_playing.insert(newid, data); 0183 valid = true; 0184 } 0185 break; 0186 } 0187 } 0188 if (!valid) { 0189 delete data; 0190 data = nullptr; 0191 } 0192 if (data) { 0193 qCDebug(OkularCoreDebug) << "PLAY"; 0194 data->play(); 0195 m_state = AudioPlayer::PlayingState; 0196 } 0197 return valid; 0198 } 0199 0200 void AudioPlayerPrivate::stopPlayings() 0201 { 0202 qDeleteAll(m_playing); 0203 m_playing.clear(); 0204 m_state = AudioPlayer::StoppedState; 0205 } 0206 0207 void AudioPlayerPrivate::finished(int id) 0208 { 0209 QHash<int, PlayData *>::iterator it = m_playing.find(id); 0210 if (it == m_playing.end()) { 0211 return; 0212 } 0213 0214 SoundInfo si = it.value()->m_info; 0215 // if the sound must be repeated indefinitely, then start the playback 0216 // again, otherwise destroy the PlayData as it's no more useful 0217 if (si.repeat) { 0218 it.value()->play(); 0219 } else { 0220 delete it.value(); 0221 m_playing.erase(it); 0222 m_state = AudioPlayer::StoppedState; 0223 } 0224 qCDebug(OkularCoreDebug) << "finished," << m_playing.count(); 0225 } 0226 0227 AudioPlayer::AudioPlayer() 0228 : QObject() 0229 , d(new AudioPlayerPrivate(this)) 0230 { 0231 } 0232 0233 AudioPlayer::~AudioPlayer() 0234 { 0235 delete d; 0236 } 0237 0238 AudioPlayer *AudioPlayer::instance() 0239 { 0240 static AudioPlayer ap; 0241 return ≈ 0242 } 0243 0244 void AudioPlayer::playSound(const Sound *sound, const SoundAction *linksound) 0245 { 0246 // we can't play null pointers ;) 0247 if (!sound) { 0248 return; 0249 } 0250 0251 // we don't play external sounds for remote documents 0252 if (sound->soundType() == Sound::External && !d->m_currentDocument.isLocalFile()) { 0253 return; 0254 } 0255 0256 qCDebug(OkularCoreDebug); 0257 SoundInfo si(sound, linksound); 0258 0259 // if the mix flag of the new sound is false, then the currently playing 0260 // sounds must be stopped. 0261 if (!si.mix) { 0262 d->stopPlayings(); 0263 } 0264 0265 d->play(si); 0266 } 0267 0268 void AudioPlayer::stopPlaybacks() 0269 { 0270 d->stopPlayings(); 0271 } 0272 0273 AudioPlayer::State AudioPlayer::state() const 0274 { 0275 return d->m_state; 0276 } 0277 0278 void AudioPlayer::resetDocument() 0279 { 0280 d->m_currentDocument = {}; 0281 } 0282 0283 void AudioPlayer::setDocument(const QUrl &url, Okular::Document *document) 0284 { 0285 Q_UNUSED(document); 0286 d->m_currentDocument = url; 0287 } 0288 0289 #else 0290 0291 namespace Okular 0292 { 0293 class AudioPlayerPrivate 0294 { 0295 public: 0296 Document *document; 0297 }; 0298 } 0299 0300 AudioPlayer::AudioPlayer() 0301 : d(new AudioPlayerPrivate()) 0302 { 0303 } 0304 0305 AudioPlayer *AudioPlayer::instance() 0306 { 0307 static AudioPlayer ap; 0308 return ≈ 0309 } 0310 0311 void AudioPlayer::playSound(const Sound *sound, const SoundAction *linksound) 0312 { 0313 Q_UNUSED(sound); 0314 Q_UNUSED(linksound); 0315 Q_EMIT d->document->warning(i18n("This Okular is built without audio support"), 2000); 0316 } 0317 0318 AudioPlayer::State Okular::AudioPlayer::state() const 0319 { 0320 return State::StoppedState; 0321 } 0322 0323 void AudioPlayer::stopPlaybacks() 0324 { 0325 } 0326 0327 AudioPlayer::~AudioPlayer() noexcept 0328 { 0329 } 0330 0331 void AudioPlayer::resetDocument() 0332 { 0333 d->document = nullptr; 0334 } 0335 0336 void AudioPlayer::setDocument(const QUrl &url, Okular::Document *document) 0337 { 0338 Q_UNUSED(url); 0339 d->document = document; 0340 } 0341 0342 #endif 0343 0344 #include "moc_audioplayer.cpp"