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