Okular

audioplayer.cpp
1 /*
2  SPDX-FileCopyrightText: 2007 Pino Toscano <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "audioplayer.h"
8 #include "audioplayer_p.h"
9 
10 // qt/kde includes
11 #include <KRandom>
12 #include <QBuffer>
13 #include <QDebug>
14 #include <QDir>
15 #include <phonon/abstractmediastream.h>
16 #include <phonon/audiooutput.h>
17 #include <phonon/mediaobject.h>
18 #include <phonon/path.h>
19 
20 // local includes
21 #include "action.h"
22 #include "debug_p.h"
23 #include "sound.h"
24 
25 using namespace Okular;
26 
27 // helper class used to store info about a sound to be played
28 class SoundInfo
29 {
30 public:
31  explicit SoundInfo(const Sound *s = nullptr, const SoundAction *ls = nullptr)
32  : sound(s)
33  , volume(0.5)
34  , synchronous(false)
35  , repeat(false)
36  , mix(false)
37  {
38  if (ls) {
39  volume = ls->volume();
40  synchronous = ls->synchronous();
41  repeat = ls->repeat();
42  mix = ls->mix();
43  }
44  }
45 
46  const Sound *sound;
47  double volume;
48  bool synchronous;
49  bool repeat;
50  bool mix;
51 };
52 
53 class PlayData
54 {
55 public:
56  PlayData()
57  : m_mediaobject(nullptr)
58  , m_output(nullptr)
59  , m_buffer(nullptr)
60  {
61  }
62 
63  void play()
64  {
65  if (m_buffer) {
66  m_buffer->open(QIODevice::ReadOnly);
67  }
68  m_mediaobject->play();
69  }
70 
71  ~PlayData()
72  {
73  m_mediaobject->stop();
74  delete m_mediaobject;
75  delete m_output;
76  delete m_buffer;
77  }
78 
79  PlayData(const PlayData &) = delete;
80  PlayData &operator=(const PlayData &) = delete;
81 
82  Phonon::MediaObject *m_mediaobject;
83  Phonon::AudioOutput *m_output;
84  QBuffer *m_buffer;
85  SoundInfo m_info;
86 };
87 
88 AudioPlayerPrivate::AudioPlayerPrivate(AudioPlayer *qq)
89  : q(qq)
90  , m_state(AudioPlayer::StoppedState)
91 {
92 }
93 
94 AudioPlayerPrivate::~AudioPlayerPrivate()
95 {
96  stopPlayings();
97 }
98 
99 int AudioPlayerPrivate::newId() const
100 {
101  int newid = 0;
103  QHash<int, PlayData *>::const_iterator itEnd = m_playing.constEnd();
104  do {
105  newid = KRandom::random();
106  it = m_playing.constFind(newid);
107  } while (it != itEnd);
108  return newid;
109 }
110 
111 bool AudioPlayerPrivate::play(const SoundInfo &si)
112 {
113  qCDebug(OkularCoreDebug);
114  PlayData *data = new PlayData();
115  data->m_output = new Phonon::AudioOutput(Phonon::NotificationCategory);
116  data->m_output->setVolume(si.volume);
117  data->m_mediaobject = new Phonon::MediaObject();
118  Phonon::createPath(data->m_mediaobject, data->m_output);
119  data->m_info = si;
120  bool valid = false;
121 
122  switch (si.sound->soundType()) {
123  case Sound::External: {
124  QString url = si.sound->url();
125  qCDebug(OkularCoreDebug) << "External," << url;
126  if (!url.isEmpty()) {
127  int newid = newId();
128  QObject::connect(data->m_mediaobject, &Phonon::MediaObject::finished, q, [this, newid]() { finished(newid); });
129  QUrl newurl;
130  if (QUrl::fromUserInput(url).isRelative()) {
131  newurl = m_currentDocument.adjusted(QUrl::RemoveFilename);
132  newurl.setPath(newurl.path() + url);
133  } else {
134  newurl = QUrl::fromLocalFile(url);
135  }
136  data->m_mediaobject->setCurrentSource(newurl);
137  m_playing.insert(newid, data);
138  valid = true;
139  }
140  break;
141  }
142  case Sound::Embedded: {
143  QByteArray filedata = si.sound->data();
144  qCDebug(OkularCoreDebug) << "Embedded," << filedata.length();
145  if (!filedata.isEmpty()) {
146  qCDebug(OkularCoreDebug) << "Mediaobject:" << data->m_mediaobject;
147  int newid = newId();
148  QObject::connect(data->m_mediaobject, &Phonon::MediaObject::finished, q, [this, newid]() { finished(newid); });
149  data->m_buffer = new QBuffer();
150  data->m_buffer->setData(filedata);
151  data->m_mediaobject->setCurrentSource(Phonon::MediaSource(data->m_buffer));
152  m_playing.insert(newid, data);
153  valid = true;
154  }
155  break;
156  }
157  }
158  if (!valid) {
159  delete data;
160  data = nullptr;
161  }
162  if (data) {
163  qCDebug(OkularCoreDebug) << "PLAY";
164  data->play();
165  m_state = AudioPlayer::PlayingState;
166  }
167  return valid;
168 }
169 
170 void AudioPlayerPrivate::stopPlayings()
171 {
172  qDeleteAll(m_playing);
173  m_playing.clear();
174  m_state = AudioPlayer::StoppedState;
175 }
176 
177 void AudioPlayerPrivate::finished(int id)
178 {
179  QHash<int, PlayData *>::iterator it = m_playing.find(id);
180  if (it == m_playing.end()) {
181  return;
182  }
183 
184  SoundInfo si = it.value()->m_info;
185  // if the sound must be repeated indefinitely, then start the playback
186  // again, otherwise destroy the PlayData as it's no more useful
187  if (si.repeat) {
188  it.value()->play();
189  } else {
190  delete it.value();
191  m_playing.erase(it);
192  m_state = AudioPlayer::StoppedState;
193  }
194  qCDebug(OkularCoreDebug) << "finished," << m_playing.count();
195 }
196 
197 AudioPlayer::AudioPlayer()
198  : QObject()
199  , d(new AudioPlayerPrivate(this))
200 {
201 }
202 
203 AudioPlayer::~AudioPlayer()
204 {
205  delete d;
206 }
207 
209 {
210  static AudioPlayer ap;
211  return &ap;
212 }
213 
214 void AudioPlayer::playSound(const Sound *sound, const SoundAction *linksound)
215 {
216  // we can't play null pointers ;)
217  if (!sound) {
218  return;
219  }
220 
221  // we don't play external sounds for remote documents
222  if (sound->soundType() == Sound::External && !d->m_currentDocument.isLocalFile()) {
223  return;
224  }
225 
226  qCDebug(OkularCoreDebug);
227  SoundInfo si(sound, linksound);
228 
229  // if the mix flag of the new sound is false, then the currently playing
230  // sounds must be stopped.
231  if (!si.mix) {
232  d->stopPlayings();
233  }
234 
235  d->play(si);
236 }
237 
239 {
240  d->stopPlayings();
241 }
242 
244 {
245  return d->m_state;
246 }
247 
248 #include "moc_audioplayer.cpp"
@ PlayingState
The AudioPlayer is playing a audio file.
Definition: audioplayer.h:40
qreal volume()
An audio player.
Definition: audioplayer.h:27
Contains information about a sound object.
Definition: sound.h:23
State
The state of AudioPlayer.
Definition: audioplayer.h:36
KGUIADDONS_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias=0.5)
The documentation to the global Okular namespace.
Definition: action.h:16
@ External
Is stored at external resource (e.g. url)
Definition: sound.h:30
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void playSound(const Sound *sound, const SoundAction *linksound=nullptr)
Enqueue the specified sound for playing, optionally taking more information about the playing from th...
@ StoppedState
The AudioPlayer isn't playing a audio file.
Definition: audioplayer.h:44
SoundType soundType() const
Returns the type of the sound object.
Definition: sound.cpp:61
QAction * repeat(const QObject *recvr, const char *slot, QObject *parent)
KCOREADDONS_EXPORT int random()
RemoveFilename
static AudioPlayer * instance()
Gets the instance of the audio player.
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
QString path(QUrl::ComponentFormattingOptions options) const const
void setPath(const QString &path, QUrl::ParsingMode mode)
The Sound action plays a sound on activation.
Definition: action.h:335
QUrl adjusted(QUrl::FormattingOptions options) const const
void stopPlaybacks()
Tell the AudioPlayer to stop all the playbacks.
@ Embedded
Is stored embedded in the document.
Definition: sound.h:31
int length() const const
QUrl fromUserInput(const QString &userInput)
State state() const
Return state of sound (playing/stopped)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Mar 27 2023 04:09:00 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.