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 &ap;
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 &ap;
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"