Okular

audioplayer.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Pino Toscano <pino@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "audioplayer.h"
8
9// qt/kde includes
10#include <KLocalizedString>
11#include <QBuffer>
12#include <QDebug>
13#include <QDir>
14#include <QRandomGenerator>
15
16#include "config-okular.h"
17
18#if HAVE_PHONON
19#include <phonon/abstractmediastream.h>
20#include <phonon/audiooutput.h>
21#include <phonon/mediaobject.h>
22#include <phonon/path.h>
23#endif
24
25// local includes
26#include "action.h"
27#include "debug_p.h"
28#include "document.h"
29#include "sound.h"
30#include <stdlib.h>
31
32using namespace Okular;
33
34#if HAVE_PHONON
35
36class PlayData;
37class SoundInfo;
38
39namespace Okular
40{
41class AudioPlayerPrivate
42{
43public:
44 explicit AudioPlayerPrivate(AudioPlayer *qq);
45
46 ~AudioPlayerPrivate();
47
48 int newId() const;
49 bool play(const SoundInfo &si);
50 void stopPlayings();
51
52 void finished(int);
53
54 AudioPlayer *q;
55
56 QHash<int, PlayData *> m_playing;
57 QUrl m_currentDocument;
58 AudioPlayer::State m_state;
59};
60}
61
62// helper class used to store info about a sound to be played
63class SoundInfo
64{
65public:
66 explicit SoundInfo(const Sound *s = nullptr, const SoundAction *ls = nullptr)
67 : sound(s)
68 , volume(0.5)
69 , synchronous(false)
70 , repeat(false)
71 , mix(false)
72 {
73 if (ls) {
74 volume = ls->volume();
75 synchronous = ls->synchronous();
76 repeat = ls->repeat();
77 mix = ls->mix();
78 }
79 }
80
81 const Sound *sound;
82 double volume;
83 bool synchronous;
84 bool repeat;
85 bool mix;
86};
87
88class PlayData
89{
90public:
91 PlayData()
92 : m_mediaobject(nullptr)
93 , m_output(nullptr)
94 , m_buffer(nullptr)
95 {
96 }
97
98 void play()
99 {
100 if (m_buffer) {
101 m_buffer->open(QIODevice::ReadOnly);
102 }
103 m_mediaobject->play();
104 }
105
106 ~PlayData()
107 {
108 m_mediaobject->stop();
109 delete m_mediaobject;
110 delete m_output;
111 delete m_buffer;
112 }
113
114 PlayData(const PlayData &) = delete;
115 PlayData &operator=(const PlayData &) = delete;
116
117 Phonon::MediaObject *m_mediaobject;
118 Phonon::AudioOutput *m_output;
119 QBuffer *m_buffer;
120 SoundInfo m_info;
121};
122
123AudioPlayerPrivate::AudioPlayerPrivate(AudioPlayer *qq)
124 : q(qq)
125 , m_state(AudioPlayer::StoppedState)
126{
127}
128
129AudioPlayerPrivate::~AudioPlayerPrivate()
130{
131 stopPlayings();
132}
133
134int AudioPlayerPrivate::newId() const
135{
136 auto random = QRandomGenerator::global();
137 int newid = 0;
139 QHash<int, PlayData *>::const_iterator itEnd = m_playing.constEnd();
140 do {
141 newid = random->bounded(RAND_MAX);
142 it = m_playing.constFind(newid);
143 } while (it != itEnd);
144 return newid;
145}
146
147bool AudioPlayerPrivate::play(const SoundInfo &si)
148{
149 qCDebug(OkularCoreDebug);
150 PlayData *data = new PlayData();
151 data->m_output = new Phonon::AudioOutput(Phonon::NotificationCategory);
152 data->m_output->setVolume(si.volume);
153 data->m_mediaobject = new Phonon::MediaObject();
154 Phonon::createPath(data->m_mediaobject, data->m_output);
155 data->m_info = si;
156 bool valid = false;
157
158 switch (si.sound->soundType()) {
159 case Sound::External: {
160 QString url = si.sound->url();
161 qCDebug(OkularCoreDebug) << "External," << url;
162 if (!url.isEmpty()) {
163 int newid = newId();
164 QObject::connect(data->m_mediaobject, &Phonon::MediaObject::finished, q, [this, newid]() { finished(newid); });
165 const QUrl newurl = QUrl::fromUserInput(url, m_currentDocument.adjusted(QUrl::RemoveFilename).toLocalFile());
166 data->m_mediaobject->setCurrentSource(newurl);
167 m_playing.insert(newid, data);
168 valid = true;
169 }
170 break;
171 }
172 case Sound::Embedded: {
173 QByteArray filedata = si.sound->data();
174 qCDebug(OkularCoreDebug) << "Embedded," << filedata.length();
175 if (!filedata.isEmpty()) {
176 qCDebug(OkularCoreDebug) << "Mediaobject:" << data->m_mediaobject;
177 int newid = newId();
178 QObject::connect(data->m_mediaobject, &Phonon::MediaObject::finished, q, [this, newid]() { finished(newid); });
179 data->m_buffer = new QBuffer();
180 data->m_buffer->setData(filedata);
181 data->m_mediaobject->setCurrentSource(Phonon::MediaSource(data->m_buffer));
182 m_playing.insert(newid, data);
183 valid = true;
184 }
185 break;
186 }
187 }
188 if (!valid) {
189 delete data;
190 data = nullptr;
191 }
192 if (data) {
193 qCDebug(OkularCoreDebug) << "PLAY";
194 data->play();
196 }
197 return valid;
198}
199
200void AudioPlayerPrivate::stopPlayings()
201{
202 qDeleteAll(m_playing);
203 m_playing.clear();
205}
206
207void AudioPlayerPrivate::finished(int id)
208{
209 QHash<int, PlayData *>::iterator it = m_playing.find(id);
210 if (it == m_playing.end()) {
211 return;
212 }
213
214 SoundInfo si = it.value()->m_info;
215 // if the sound must be repeated indefinitely, then start the playback
216 // again, otherwise destroy the PlayData as it's no more useful
217 if (si.repeat) {
218 it.value()->play();
219 } else {
220 delete it.value();
221 m_playing.erase(it);
223 }
224 qCDebug(OkularCoreDebug) << "finished," << m_playing.count();
225}
226
227AudioPlayer::AudioPlayer()
228 : QObject()
229 , d(new AudioPlayerPrivate(this))
230{
231}
232
233AudioPlayer::~AudioPlayer()
234{
235 delete d;
236}
237
239{
240 static AudioPlayer ap;
241 return &ap;
242}
243
244void AudioPlayer::playSound(const Sound *sound, const SoundAction *linksound)
245{
246 // we can't play null pointers ;)
247 if (!sound) {
248 return;
249 }
250
251 // we don't play external sounds for remote documents
252 if (sound->soundType() == Sound::External && !d->m_currentDocument.isLocalFile()) {
253 return;
254 }
255
256 qCDebug(OkularCoreDebug);
257 SoundInfo si(sound, linksound);
258
259 // if the mix flag of the new sound is false, then the currently playing
260 // sounds must be stopped.
261 if (!si.mix) {
262 d->stopPlayings();
263 }
264
265 d->play(si);
266}
267
269{
270 d->stopPlayings();
271}
272
274{
275 return d->m_state;
276}
277
278void AudioPlayer::resetDocument()
279{
280 d->m_currentDocument = {};
281}
282
283void AudioPlayer::setDocument(const QUrl &url, Okular::Document *document)
284{
285 Q_UNUSED(document);
286 d->m_currentDocument = url;
287}
288
289#else
290
291namespace Okular
292{
293class AudioPlayerPrivate
294{
295public:
296 Document *document;
297};
298}
299
300AudioPlayer::AudioPlayer()
301 : d(new AudioPlayerPrivate())
302{
303}
304
306{
307 static AudioPlayer ap;
308 return &ap;
309}
310
311void AudioPlayer::playSound(const Sound *sound, const SoundAction *linksound)
312{
313 Q_UNUSED(sound);
314 Q_UNUSED(linksound);
315 Q_EMIT d->document->warning(i18n("This Okular is built without audio support"), 2000);
316}
317
319{
320 return State::StoppedState;
321}
322
326
327AudioPlayer::~AudioPlayer() noexcept
328{
329}
330
331void AudioPlayer::resetDocument()
332{
333 d->document = nullptr;
334}
335
336void AudioPlayer::setDocument(const QUrl &url, Okular::Document *document)
337{
338 Q_UNUSED(url);
339 d->document = document;
340}
341
342#endif
343
344#include "moc_audioplayer.cpp"
An audio player.
Definition audioplayer.h:28
void playSound(const Sound *sound, const SoundAction *linksound=nullptr)
Enqueue the specified sound for playing, optionally taking more information about the playing from th...
void stopPlaybacks()
Tell the AudioPlayer to stop all the playbacks.
State state() const
Return state of sound (playing/stopped)
static AudioPlayer * instance()
Gets the instance of the audio player.
State
The state of AudioPlayer.
Definition audioplayer.h:36
@ StoppedState
The AudioPlayer isn't playing a audio file.
Definition audioplayer.h:44
@ PlayingState
The AudioPlayer is playing a audio file.
Definition audioplayer.h:40
The Document.
Definition document.h:192
void warning(const QString &text, int duration)
This signal is emitted to signal a warning.
The Sound action plays a sound on activation.
Definition action.h:336
Contains information about a sound object.
Definition sound.h:24
SoundType soundType() const
Returns the type of the sound object.
Definition sound.cpp:61
@ Embedded
Is stored embedded in the document.
Definition sound.h:31
@ External
Is stored at external resource (e.g. url)
Definition sound.h:30
QString i18n(const char *text, const TYPE &arg...)
KGUIADDONS_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias=0.5)
QAction * repeat(const QObject *recvr, const char *slot, QObject *parent)
global.h
Definition action.h:17
bool isEmpty() const const
qsizetype length() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QRandomGenerator * global()
bool isEmpty() const const
RemoveFilename
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:35 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.