1 /*
2 SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
3
4 SPDX-License-Identifier: LGPL-3.0-or-later
5 */
6
7 #include "audiowrapper.h"
8
9 #include "elisa-version.h"
10
11 #include "vlcLogging.h"
12 #include "powermanagementinterface.h"
13
14 #include <QAudio>
15 #include <QDir>
16 #include <QGuiApplication>
17
18 #if defined Q_OS_WIN
19
20 #include <basetsd.h>
21 typedef SSIZE_T ssize_t;
22
23 #endif
24
25 #include <vlc/vlc.h>
26
27 #include "config-upnp-qt.h"
28
29 class AudioWrapperPrivate
30 {
31
32 public:
33
34 PowerManagementInterface mPowerInterface;
35
36 AudioWrapper *mParent = nullptr;
37
38 libvlc_instance_t *mInstance = nullptr;
39
40 libvlc_media_player_t *mPlayer = nullptr;
41
42 libvlc_event_manager_t *mPlayerEventManager = nullptr;
43
44 libvlc_media_t *mMedia = nullptr;
45
46 qint64 mMediaDuration = 0;
47
48 QMediaPlayer::State mPreviousPlayerState = QMediaPlayer::StoppedState;
49
50 QMediaPlayer::MediaStatus mPreviousMediaStatus = QMediaPlayer::NoMedia;
51
52 qreal mPreviousVolume = 100.0;
53
54 qint64 mSavedPosition = 0.0;
55
56 qint64 mUndoSavedPosition = 0.0;
57
58 qint64 mPreviousPosition = 0;
59
60 QMediaPlayer::Error mError = QMediaPlayer::NoError;
61
62 bool mIsMuted = false;
63
64 bool mIsSeekable = false;
65
66 bool mHasSavedPosition = false;
67
68 void vlcEventCallback(const struct libvlc_event_t *p_event);
69
70 void mediaIsEnded();
71
72 bool signalPlaybackChange(QMediaPlayer::State newPlayerState);
73
74 void signalMediaStatusChange(QMediaPlayer::MediaStatus newMediaStatus);
75
76 void signalVolumeChange(int newVolume);
77
78 void signalMutedChange(bool isMuted);
79
80 void signalDurationChange(libvlc_time_t newDuration);
81
82 void signalPositionChange(float newPosition);
83
84 void signalSeekableChange(bool isSeekable);
85
86 void signalErrorChange(QMediaPlayer::Error errorCode);
87
88 };
89
vlc_callback(const struct libvlc_event_t * p_event,void * p_data)90 static void vlc_callback(const struct libvlc_event_t *p_event, void *p_data)
91 {
92 reinterpret_cast<AudioWrapperPrivate*>(p_data)->vlcEventCallback(p_event);
93 }
94
AudioWrapper(QObject * parent)95 AudioWrapper::AudioWrapper(QObject *parent) : QObject(parent), d(std::make_unique<AudioWrapperPrivate>())
96 {
97 d->mParent = this;
98 d->mInstance = libvlc_new(0, nullptr);
99 libvlc_set_user_agent(d->mInstance, QGuiApplication::applicationDisplayName().toUtf8().constData(), "Elisa Music Player");
100 libvlc_set_app_id(d->mInstance, "org.kde.elisa", ELISA_VERSION_STRING, "elisa");
101
102 d->mPlayer = libvlc_media_player_new(d->mInstance);
103
104 if (!d->mPlayer) {
105 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapper::AudioWrapper" << "failed creating player" << libvlc_errmsg();
106 return;
107 }
108
109 d->mPlayerEventManager = libvlc_media_player_event_manager(d->mPlayer);
110
111 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerOpening, &vlc_callback, d.get());
112 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerBuffering, &vlc_callback, d.get());
113 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerPlaying, &vlc_callback, d.get());
114 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerPaused, &vlc_callback, d.get());
115 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerStopped, &vlc_callback, d.get());
116 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerEndReached, &vlc_callback, d.get());
117 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerEncounteredError, &vlc_callback, d.get());
118 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerPositionChanged, &vlc_callback, d.get());
119 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerSeekableChanged, &vlc_callback, d.get());
120 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerLengthChanged, &vlc_callback, d.get());
121 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerMuted, &vlc_callback, d.get());
122 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerUnmuted, &vlc_callback, d.get());
123 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerAudioVolume, &vlc_callback, d.get());
124 libvlc_event_attach(d->mPlayerEventManager, libvlc_MediaPlayerAudioDevice, &vlc_callback, d.get());
125 }
126
~AudioWrapper()127 AudioWrapper::~AudioWrapper()
128 {
129 if (d->mInstance) {
130 d->mPowerInterface.setPreventSleep(false);
131 if (d->mPlayer && d->mPreviousPlayerState != QMediaPlayer::StoppedState) {
132 libvlc_media_player_stop(d->mPlayer);
133 }
134 libvlc_release(d->mInstance);
135 }
136 }
137
muted() const138 bool AudioWrapper::muted() const
139 {
140 return d->mIsMuted;
141 }
142
volume() const143 qreal AudioWrapper::volume() const
144 {
145 if (!d->mPlayer) {
146 return 100.0;
147 }
148
149 return d->mPreviousVolume;
150 }
151
source() const152 QUrl AudioWrapper::source() const
153 {
154 if (!d->mPlayer) {
155 return {};
156 }
157 if (d->mMedia) {
158 auto filePath = QString::fromUtf8(libvlc_media_get_mrl(d->mMedia));
159 return QUrl::fromUserInput(filePath);
160 }
161 return {};
162 }
163
error() const164 QMediaPlayer::Error AudioWrapper::error() const
165 {
166 return d->mError;
167 }
168
duration() const169 qint64 AudioWrapper::duration() const
170 {
171 return d->mMediaDuration;
172 }
173
position() const174 qint64 AudioWrapper::position() const
175 {
176 if (!d->mPlayer) {
177 return 0;
178 }
179
180 if (d->mMediaDuration == -1) {
181 return 0;
182 }
183
184 return qRound64(libvlc_media_player_get_position(d->mPlayer) * d->mMediaDuration);
185 }
186
seekable() const187 bool AudioWrapper::seekable() const
188 {
189 return d->mIsSeekable;
190 }
191
playbackState() const192 QMediaPlayer::State AudioWrapper::playbackState() const
193 {
194 return d->mPreviousPlayerState;
195 }
196
status() const197 QMediaPlayer::MediaStatus AudioWrapper::status() const
198 {
199 return d->mPreviousMediaStatus;
200 }
201
setMuted(bool muted)202 void AudioWrapper::setMuted(bool muted)
203 {
204 if (!d->mPlayer) {
205 return;
206 }
207
208 libvlc_audio_set_mute(d->mPlayer, muted);
209 }
210
setVolume(qreal volume)211 void AudioWrapper::setVolume(qreal volume)
212 {
213 if (!d->mPlayer) {
214 return;
215 }
216
217 //auto realVolume = static_cast<qreal>(QAudio::convertVolume(volume / 100.0, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale));
218 libvlc_audio_set_volume(d->mPlayer, qRound(volume));
219 }
220
setSource(const QUrl & source)221 void AudioWrapper::setSource(const QUrl &source)
222 {
223 if (source.isLocalFile()) {
224 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapper::setSource reading local resource";
225 d->mMedia = libvlc_media_new_path(d->mInstance, QDir::toNativeSeparators(source.toLocalFile()).toUtf8().constData());
226 } else {
227 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapper::setSource reading remote resource";
228 d->mMedia = libvlc_media_new_location(d->mInstance, source.url().toUtf8().constData());
229 }
230
231 if (!d->mMedia) {
232 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapper::setSource"
233 << "failed creating media"
234 << libvlc_errmsg()
235 << QDir::toNativeSeparators(source.toLocalFile()).toUtf8().constData();
236
237 d->mMedia = libvlc_media_new_path(d->mInstance, QDir::toNativeSeparators(source.toLocalFile()).toLatin1().constData());
238 if (!d->mMedia) {
239 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapper::setSource"
240 << "failed creating media"
241 << libvlc_errmsg()
242 << QDir::toNativeSeparators(source.toLocalFile()).toLatin1().constData();
243 return;
244 }
245 }
246
247 libvlc_media_player_set_media(d->mPlayer, d->mMedia);
248
249 if (d->signalPlaybackChange(QMediaPlayer::StoppedState)) {
250 Q_EMIT stopped();
251 }
252
253 d->signalMediaStatusChange(QMediaPlayer::LoadingMedia);
254 d->signalMediaStatusChange(QMediaPlayer::LoadedMedia);
255 d->signalMediaStatusChange(QMediaPlayer::BufferedMedia);
256
257 d->mHasSavedPosition = false;
258 }
259
setPosition(qint64 position)260 void AudioWrapper::setPosition(qint64 position)
261 {
262 if (!d->mPlayer) {
263 return;
264 }
265
266 if (d->mMediaDuration == -1 || d->mMediaDuration == 0) {
267 savePosition(position);
268 return;
269 }
270
271 libvlc_media_player_set_position(d->mPlayer, static_cast<float>(position) / d->mMediaDuration);
272 }
273
savePosition(qint64 position)274 void AudioWrapper::savePosition(qint64 position)
275 {
276 if (!d->mHasSavedPosition) {
277 d->mHasSavedPosition = true;
278 d->mSavedPosition = position;
279 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapper::savePosition" << "restore old position" << d->mSavedPosition;
280 }
281 }
282
saveUndoPosition(qint64 position)283 void AudioWrapper::saveUndoPosition(qint64 position)
284 {
285 d->mUndoSavedPosition = position;
286 }
287
restoreUndoPosition()288 void AudioWrapper::restoreUndoPosition()
289 {
290 d->mHasSavedPosition = true;
291 d->mSavedPosition = d->mUndoSavedPosition;
292 }
293
play()294 void AudioWrapper::play()
295 {
296 if (!d->mPlayer) {
297 return;
298 }
299
300 libvlc_media_player_play(d->mPlayer);
301 }
302
pause()303 void AudioWrapper::pause()
304 {
305 if (!d->mPlayer) {
306 return;
307 }
308
309 libvlc_media_player_pause(d->mPlayer);
310 }
311
stop()312 void AudioWrapper::stop()
313 {
314 if (!d->mPlayer) {
315 return;
316 }
317
318 libvlc_media_player_stop(d->mPlayer);
319 }
320
seek(qint64 position)321 void AudioWrapper::seek(qint64 position)
322 {
323 setPosition(position);
324 }
325
mediaStatusChanged()326 void AudioWrapper::mediaStatusChanged()
327 {
328 }
329
playerStateChanged()330 void AudioWrapper::playerStateChanged()
331 {
332 }
333
playerMutedChanged()334 void AudioWrapper::playerMutedChanged()
335 {
336 }
337
playerVolumeChanged()338 void AudioWrapper::playerVolumeChanged()
339 {
340 }
341
playerStateSignalChanges(QMediaPlayer::State newState)342 void AudioWrapper::playerStateSignalChanges(QMediaPlayer::State newState)
343 {
344 QMetaObject::invokeMethod(this, [this, newState]() {
345 Q_EMIT playbackStateChanged(newState);
346 switch (newState)
347 {
348 case QMediaPlayer::StoppedState:
349 Q_EMIT stopped();
350 d->mPowerInterface.setPreventSleep(false);
351 break;
352 case QMediaPlayer::PlayingState:
353 Q_EMIT playing();
354 d->mPowerInterface.setPreventSleep(true);
355 break;
356 case QMediaPlayer::PausedState:
357 Q_EMIT paused();
358 d->mPowerInterface.setPreventSleep(false);
359 break;
360 }
361 }, Qt::QueuedConnection);
362 }
363
mediaStatusSignalChanges(QMediaPlayer::MediaStatus newStatus)364 void AudioWrapper::mediaStatusSignalChanges(QMediaPlayer::MediaStatus newStatus)
365 {
366 QMetaObject::invokeMethod(this, [this, newStatus]() {Q_EMIT statusChanged(newStatus);}, Qt::QueuedConnection);
367 }
368
playerErrorSignalChanges(QMediaPlayer::Error error)369 void AudioWrapper::playerErrorSignalChanges(QMediaPlayer::Error error)
370 {
371 QMetaObject::invokeMethod(this, [this, error]() {Q_EMIT errorChanged(error);}, Qt::QueuedConnection);
372 }
373
playerDurationSignalChanges(qint64 newDuration)374 void AudioWrapper::playerDurationSignalChanges(qint64 newDuration)
375 {
376 QMetaObject::invokeMethod(this, [this, newDuration]() {Q_EMIT durationChanged(newDuration);}, Qt::QueuedConnection);
377 }
378
playerPositionSignalChanges(qint64 newPosition)379 void AudioWrapper::playerPositionSignalChanges(qint64 newPosition)
380 {
381 QMetaObject::invokeMethod(this, [this, newPosition]() {Q_EMIT positionChanged(newPosition);}, Qt::QueuedConnection);
382 }
383
playerVolumeSignalChanges()384 void AudioWrapper::playerVolumeSignalChanges()
385 {
386 QMetaObject::invokeMethod(this, [this]() {Q_EMIT volumeChanged();}, Qt::QueuedConnection);
387 }
388
playerMutedSignalChanges(bool isMuted)389 void AudioWrapper::playerMutedSignalChanges(bool isMuted)
390 {
391 QMetaObject::invokeMethod(this, [this, isMuted]() {Q_EMIT mutedChanged(isMuted);}, Qt::QueuedConnection);
392 }
393
playerSeekableSignalChanges(bool isSeekable)394 void AudioWrapper::playerSeekableSignalChanges(bool isSeekable)
395 {
396 QMetaObject::invokeMethod(this, [this, isSeekable]() {Q_EMIT seekableChanged(isSeekable);}, Qt::QueuedConnection);
397 }
398
vlcEventCallback(const struct libvlc_event_t * p_event)399 void AudioWrapperPrivate::vlcEventCallback(const struct libvlc_event_t *p_event)
400 {
401 const auto eventType = static_cast<libvlc_event_e>(p_event->type);
402
403 switch(eventType)
404 {
405 case libvlc_MediaPlayerOpening:
406 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerOpening";
407 signalMediaStatusChange(QMediaPlayer::LoadedMedia);
408 break;
409 case libvlc_MediaPlayerBuffering:
410 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerBuffering";
411 signalMediaStatusChange(QMediaPlayer::BufferedMedia);
412 break;
413 case libvlc_MediaPlayerPlaying:
414 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerPlaying";
415 signalPlaybackChange(QMediaPlayer::PlayingState);
416 break;
417 case libvlc_MediaPlayerPaused:
418 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerPaused";
419 signalPlaybackChange(QMediaPlayer::PausedState);
420 break;
421 case libvlc_MediaPlayerStopped:
422 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerStopped";
423 signalPlaybackChange(QMediaPlayer::StoppedState);
424 break;
425 case libvlc_MediaPlayerEndReached:
426 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerEndReached";
427 signalMediaStatusChange(QMediaPlayer::BufferedMedia);
428 signalMediaStatusChange(QMediaPlayer::NoMedia);
429 signalMediaStatusChange(QMediaPlayer::EndOfMedia);
430 mediaIsEnded();
431 break;
432 case libvlc_MediaPlayerEncounteredError:
433 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerEncounteredError";
434 signalErrorChange(QMediaPlayer::ResourceError);
435 mediaIsEnded();
436 signalMediaStatusChange(QMediaPlayer::InvalidMedia);
437 break;
438 case libvlc_MediaPlayerPositionChanged:
439 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerPositionChanged";
440 signalPositionChange(p_event->u.media_player_position_changed.new_position);
441 break;
442 case libvlc_MediaPlayerSeekableChanged:
443 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerSeekableChanged";
444 signalSeekableChange(p_event->u.media_player_seekable_changed.new_seekable);
445 break;
446 case libvlc_MediaPlayerLengthChanged:
447 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerLengthChanged";
448 signalDurationChange(p_event->u.media_player_length_changed.new_length);
449 if (mHasSavedPosition) {
450 mParent->setPosition(mSavedPosition);
451 mHasSavedPosition = false;
452 }
453 break;
454 case libvlc_MediaPlayerMuted:
455 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerMuted";
456 signalMutedChange(true);
457 break;
458 case libvlc_MediaPlayerUnmuted:
459 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerUnmuted";
460 signalMutedChange(false);
461 break;
462 case libvlc_MediaPlayerAudioVolume:
463 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerAudioVolume";
464 signalVolumeChange(qRound(p_event->u.media_player_audio_volume.volume * 100));
465 break;
466 case libvlc_MediaPlayerAudioDevice:
467 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "libvlc_MediaPlayerAudioDevice";
468 break;
469 default:
470 qCDebug(orgKdeElisaPlayerVlc) << "AudioWrapperPrivate::vlcEventCallback" << "eventType" << eventType;
471 break;
472 }
473 }
474
mediaIsEnded()475 void AudioWrapperPrivate::mediaIsEnded()
476 {
477 libvlc_media_release(mMedia);
478 mMedia = nullptr;
479 }
480
signalPlaybackChange(QMediaPlayer::State newPlayerState)481 bool AudioWrapperPrivate::signalPlaybackChange(QMediaPlayer::State newPlayerState)
482 {
483 if (mPreviousPlayerState != newPlayerState) {
484 mPreviousPlayerState = newPlayerState;
485
486 mParent->playerStateSignalChanges(mPreviousPlayerState);
487
488 return true;
489 }
490
491 return false;
492 }
493
signalMediaStatusChange(QMediaPlayer::MediaStatus newMediaStatus)494 void AudioWrapperPrivate::signalMediaStatusChange(QMediaPlayer::MediaStatus newMediaStatus)
495 {
496 if (mPreviousMediaStatus != newMediaStatus) {
497 mPreviousMediaStatus = newMediaStatus;
498
499 mParent->mediaStatusSignalChanges(mPreviousMediaStatus);
500 }
501 }
502
signalVolumeChange(int newVolume)503 void AudioWrapperPrivate::signalVolumeChange(int newVolume)
504 {
505 if (newVolume == -1) {
506 return;
507 }
508
509 if (mPreviousPlayerState == QMediaPlayer::StoppedState) {
510 return;
511 }
512
513 if (abs(int(mPreviousVolume - newVolume)) > 0.01) {
514 mPreviousVolume = newVolume;
515
516 mParent->playerVolumeSignalChanges();
517 }
518 }
519
signalMutedChange(bool isMuted)520 void AudioWrapperPrivate::signalMutedChange(bool isMuted)
521 {
522 if (mIsMuted != isMuted) {
523 mIsMuted = isMuted;
524
525 mParent->playerMutedSignalChanges(mIsMuted);
526 }
527 }
528
signalDurationChange(libvlc_time_t newDuration)529 void AudioWrapperPrivate::signalDurationChange(libvlc_time_t newDuration)
530 {
531 if (mMediaDuration != newDuration) {
532 mMediaDuration = newDuration;
533
534 mParent->playerDurationSignalChanges(mMediaDuration);
535 }
536 }
537
signalPositionChange(float newPosition)538 void AudioWrapperPrivate::signalPositionChange(float newPosition)
539 {
540 if (mMediaDuration == -1) {
541 return;
542 }
543
544 auto computedPosition = qRound64(newPosition * mMediaDuration);
545
546 if (mPreviousPosition != computedPosition) {
547 mPreviousPosition = computedPosition;
548
549 mParent->playerPositionSignalChanges(mPreviousPosition);
550 }
551
552 if (this->mMedia) {
553 QString title = QLatin1String(libvlc_media_get_meta(this->mMedia, libvlc_meta_Title));
554 QString nowPlaying = QLatin1String(libvlc_media_get_meta(this->mMedia, libvlc_meta_NowPlaying));
555
556 Q_EMIT mParent->currentPlayingForRadiosChanged(title, nowPlaying);
557 }
558 }
559
signalSeekableChange(bool isSeekable)560 void AudioWrapperPrivate::signalSeekableChange(bool isSeekable)
561 {
562 if (mIsSeekable != isSeekable) {
563 mIsSeekable = isSeekable;
564
565 mParent->playerSeekableSignalChanges(isSeekable);
566 }
567 }
568
signalErrorChange(QMediaPlayer::Error errorCode)569 void AudioWrapperPrivate::signalErrorChange(QMediaPlayer::Error errorCode)
570 {
571 if (mError != errorCode) {
572 mError = errorCode;
573
574 mParent->playerErrorSignalChanges(errorCode);
575 }
576 }
577
578
579 #include "moc_audiowrapper.cpp"
580