1 /*
2     Copyright (C) 2007-2008 Tanguy Krotoff <tkrotoff@gmail.com>
3     Copyright (C) 2008 Lukas Durfina <lukas.durfina@gmail.com>
4     Copyright (C) 2009 Fathi Boudra <fabo@kde.org>
5     Copyright (C) 2010 Ben Cooksley <sourtooth@gmail.com>
6     Copyright (C) 2009-2011 vlc-phonon AUTHORS <kde-multimedia@kde.org>
7     Copyright (C) 2010-2021 Harald Sitter <sitter@kde.org>
8 
9     This library is free software; you can redistribute it and/or
10     modify it under the terms of the GNU Lesser General Public
11     License as published by the Free Software Foundation; either
12     version 2.1 of the License, or (at your option) any later version.
13 
14     This library is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17     Lesser General Public License for more details.
18 
19     You should have received a copy of the GNU Lesser General Public
20     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
21 */
22 
23 #include "mediaobject.h"
24 
25 #include <QtCore/QDir>
26 #include <QtCore/QStringBuilder>
27 #include <QtCore/QUrl>
28 
29 #include <phonon/pulsesupport.h>
30 
31 #include <vlc/libvlc_version.h>
32 #include <vlc/vlc.h>
33 
34 #include "utils/debug.h"
35 #include "utils/libvlc.h"
36 #include "media.h"
37 #include "sinknode.h"
38 #include "streamreader.h"
39 
40 //Time in milliseconds before sending aboutToFinish() signal
41 //2 seconds
42 static const int ABOUT_TO_FINISH_TIME = 2000;
43 
44 namespace Phonon {
45 namespace VLC {
46 
MediaObject(QObject * parent)47 MediaObject::MediaObject(QObject *parent)
48     : QObject(parent)
49     , m_nextSource(MediaSource(QUrl()))
50     , m_streamReader(0)
51     , m_state(Phonon::StoppedState)
52     , m_tickInterval(0)
53     , m_transitionTime(0)
54     , m_media(0)
55 {
56     qRegisterMetaType<QMultiMap<QString, QString> >("QMultiMap<QString, QString>");
57 
58     m_player = new MediaPlayer(this);
59     Q_ASSERT(m_player);
60     if (!m_player->libvlc_media_player())
61         error() << "libVLC:" << LibVLC::errorMessage();
62 
63     // Player signals.
64     connect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
65     connect(m_player, SIGNAL(timeChanged(qint64)), this, SLOT(timeChanged(qint64)));
66     connect(m_player, SIGNAL(stateChanged(MediaPlayer::State)), this, SLOT(updateState(MediaPlayer::State)));
67     connect(m_player, SIGNAL(hasVideoChanged(bool)), this, SLOT(onHasVideoChanged(bool)));
68     connect(m_player, SIGNAL(bufferChanged(int)), this, SLOT(setBufferStatus(int)));
69     connect(m_player, SIGNAL(timeChanged(qint64)), this, SLOT(timeChanged(qint64)));
70 
71     // Internal Signals.
72     connect(this, SIGNAL(moveToNext()), SLOT(moveToNextSource()));
73     connect(m_refreshTimer, SIGNAL(timeout()), this, SLOT(refreshDescriptors()));
74 
75     resetMembers();
76 }
77 
~MediaObject()78 MediaObject::~MediaObject()
79 {
80     unloadMedia();
81     // Shutdown the pulseaudio mainloop before the MediaPlayer gets destroyed
82     // (it is a child of the MO). There appears to be a peculiar race condition
83     // between the pa_thread_mainloop used by VLC and the pa_glib_mainloop used
84     // by Phonon's PulseSupport where for a very short time frame after the
85     // former was stopped and freed the latter can run and fall over
86     //   Invalid read from eventfd: Bad file descriptor
87     //   Code should not be reached at pulsecore/fdsem.c:157, function flush(). Aborting.
88     // Since we don't use PulseSupport since VLC 2.2 we can simply force a
89     // loop shutdown even when the application isn't about to terminate.
90     // The instance gets created again anyway.
91     PulseSupport::shutdown();
92 }
93 
resetMembers()94 void MediaObject::resetMembers()
95 {
96     // default to -1, so that streams won't break and to comply with the docs (-1 if unknown)
97     m_totalTime = -1;
98     m_hasVideo = false;
99     m_seekpoint = 0;
100 
101     m_prefinishEmitted = false;
102     m_aboutToFinishEmitted = false;
103 
104     m_lastTick = 0;
105 
106     m_timesVideoChecked = 0;
107 
108     m_buffering = false;
109     m_stateAfterBuffering = ErrorState;
110 
111     resetMediaController();
112 
113     // Forcefully shutdown plusesupport to prevent crashing between the PS PA glib mainloop
114     // and the VLC PA threaded mainloop. See destructor.
115     PulseSupport::shutdown();
116 }
117 
play()118 void MediaObject::play()
119 {
120     DEBUG_BLOCK;
121 
122     switch (m_state) {
123     case PlayingState:
124         // Do not do anything if we are already playing (as per documentation).
125         return;
126     case PausedState:
127         m_player->resume();
128         break;
129     default:
130         setupMedia();
131         if (m_player->play())
132             error() << "libVLC:" << LibVLC::errorMessage();
133         break;
134     }
135 }
136 
pause()137 void MediaObject::pause()
138 {
139     DEBUG_BLOCK;
140     switch (m_state) {
141     case BufferingState:
142     case PlayingState:
143         m_player->pause();
144         break;
145     case PausedState:
146         return;
147     default:
148         debug() << "doing paused play";
149         setupMedia();
150         m_player->pausedPlay();
151         break;
152     }
153 }
154 
stop()155 void MediaObject::stop()
156 {
157     DEBUG_BLOCK;
158     if (m_streamReader)
159         m_streamReader->unlock();
160     m_nextSource = MediaSource(QUrl());
161     m_player->stop();
162 }
163 
seek(qint64 milliseconds)164 void MediaObject::seek(qint64 milliseconds)
165 {
166     DEBUG_BLOCK;
167 
168     switch (m_state) {
169     case PlayingState:
170     case PausedState:
171     case BufferingState:
172         break;
173     default:
174         // Seeking while not being in a playingish state is cached for later.
175         m_seekpoint = milliseconds;
176         return;
177     }
178 
179     debug() << "seeking" << milliseconds << "msec";
180 
181     m_player->setTime(milliseconds);
182 
183     const qint64 time = currentTime();
184     const qint64 total = totalTime();
185 
186     // Reset last tick marker so we emit time even after seeking
187     if (time < m_lastTick)
188         m_lastTick = time;
189     if (time < total - m_prefinishMark)
190         m_prefinishEmitted = false;
191     if (time < total - ABOUT_TO_FINISH_TIME)
192         m_aboutToFinishEmitted = false;
193 }
194 
timeChanged(qint64 time)195 void MediaObject::timeChanged(qint64 time)
196 {
197     const qint64 totalTime = m_totalTime;
198 
199     switch (m_state) {
200     case PlayingState:
201     case BufferingState:
202     case PausedState:
203         emitTick(time);
204     default:
205         break;
206     }
207 
208     if (m_state == PlayingState || m_state == BufferingState) { // Buffering is concurrent
209         if (time >= totalTime - m_prefinishMark) {
210             if (!m_prefinishEmitted) {
211                 m_prefinishEmitted = true;
212                 emit prefinishMarkReached(totalTime - time);
213             }
214         }
215         // Note that when the totalTime is <= 0 we cannot calculate any sane delta.
216         if (totalTime > 0 && time >= totalTime - ABOUT_TO_FINISH_TIME)
217             emitAboutToFinish();
218     }
219 }
220 
emitTick(qint64 time)221 void MediaObject::emitTick(qint64 time)
222 {
223     if (m_tickInterval == 0) // Make sure we do not ever emit ticks when deactivated.\]
224         return;
225     if (time + m_tickInterval >= m_lastTick) {
226         m_lastTick = time;
227         emit tick(time);
228     }
229 }
230 
loadMedia(const QByteArray & mrl)231 void MediaObject::loadMedia(const QByteArray &mrl)
232 {
233     DEBUG_BLOCK;
234 
235     // Initial state is loading, from which we quickly progress to stopped because
236     // libvlc does not provide feedback on loading and the media does not get loaded
237     // until we play it.
238     // FIXME: libvlc should really allow for this as it can cause unexpected delay
239     // even though the GUI might indicate that playback should start right away.
240     changeState(Phonon::LoadingState);
241 
242     m_mrl = mrl;
243     debug() << "loading encoded:" << m_mrl;
244 
245     // We do not have a loading state generally speaking, usually the backend
246     // is exepected to go to loading state and then at some point reach stopped,
247     // at which point playback can be started.
248     // See state enum documentation for more information.
249     changeState(Phonon::StoppedState);
250 }
251 
loadMedia(const QString & mrl)252 void MediaObject::loadMedia(const QString &mrl)
253 {
254     loadMedia(mrl.toUtf8());
255 }
256 
tickInterval() const257 qint32 MediaObject::tickInterval() const
258 {
259     return m_tickInterval;
260 }
261 
262 /**
263  * Supports runtime changes.
264  * If the user goes to tick(0) we stop the timer, otherwise we fire it up.
265  */
setTickInterval(qint32 interval)266 void MediaObject::setTickInterval(qint32 interval)
267 {
268     m_tickInterval = interval;
269 }
270 
currentTime() const271 qint64 MediaObject::currentTime() const
272 {
273     qint64 time = -1;
274 
275     switch (state()) {
276     case Phonon::PausedState:
277     case Phonon::BufferingState:
278     case Phonon::PlayingState:
279         time = m_player->time();
280         break;
281     case Phonon::StoppedState:
282     case Phonon::LoadingState:
283         time = 0;
284         break;
285     case Phonon::ErrorState:
286         time = -1;
287         break;
288     }
289 
290     return time;
291 }
292 
state() const293 Phonon::State MediaObject::state() const
294 {
295     return m_state;
296 }
297 
errorType() const298 Phonon::ErrorType MediaObject::errorType() const
299 {
300     return Phonon::NormalError;
301 }
302 
source() const303 MediaSource MediaObject::source() const
304 {
305     return m_mediaSource;
306 }
307 
setSource(const MediaSource & source)308 void MediaObject::setSource(const MediaSource &source)
309 {
310     DEBUG_BLOCK;
311 
312     // Reset previous streamereaders
313     if (m_streamReader) {
314         m_streamReader->unlock();
315         delete m_streamReader;
316         m_streamReader = 0;
317         // For streamreaders we exchanage the player's seekability with the
318         // reader's so here we change it back.
319         // Note: the reader auto-disconnects due to destruction.
320         connect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
321     }
322 
323     // Reset previous isScreen flag
324     m_isScreen = false;
325 
326     m_mediaSource = source;
327 
328     QByteArray url;
329     switch (source.type()) {
330     case MediaSource::Invalid:
331         error() << Q_FUNC_INFO << "MediaSource Type is Invalid:" << source.type();
332         break;
333     case MediaSource::Empty:
334         error() << Q_FUNC_INFO << "MediaSource is empty.";
335         break;
336     case MediaSource::LocalFile:
337     case MediaSource::Url:
338         debug() << "MediaSource::Url:" << source.url();
339         if (source.url().scheme().isEmpty()) {
340             url = "file://";
341             // QUrl considers url.scheme.isEmpty() == url.isRelative(),
342             // so to be sure the url is not actually absolute we just
343             // check the first character
344             if (!source.url().toString().startsWith('/'))
345                 url.append(QFile::encodeName(QDir::currentPath()) + '/');
346         }
347         url += source.url().toEncoded();
348         loadMedia(url);
349         break;
350     case MediaSource::Disc:
351         switch (source.discType()) {
352         case Phonon::NoDisc:
353             error() << Q_FUNC_INFO << "the MediaSource::Disc doesn't specify which one (Phonon::NoDisc)";
354             return;
355         case Phonon::Cd:
356             loadMedia(QLatin1Literal("cdda://") % m_mediaSource.deviceName());
357             break;
358         case Phonon::Dvd:
359             loadMedia(QLatin1Literal("dvd://") % m_mediaSource.deviceName());
360             break;
361         case Phonon::Vcd:
362             loadMedia(QLatin1Literal("vcd://") % m_mediaSource.deviceName());
363             break;
364         case Phonon::BluRay:
365             loadMedia(QLatin1Literal("bluray://") % m_mediaSource.deviceName());
366             break;
367         }
368         break;
369     case MediaSource::CaptureDevice: {
370         QByteArray driverName;
371         QString deviceName;
372 
373         if (source.deviceAccessList().isEmpty()) {
374             error() << Q_FUNC_INFO << "No device access list for this capture device";
375             break;
376         }
377 
378         // TODO try every device in the access list until it works, not just the first one
379         driverName = source.deviceAccessList().first().first;
380         deviceName = source.deviceAccessList().first().second;
381 
382         if (driverName == QByteArray("v4l2")) {
383             loadMedia(QLatin1Literal("v4l2://") % deviceName);
384         } else if (driverName == QByteArray("alsa")) {
385             /*
386              * Replace "default" and "plughw" and "x-phonon" with "hw" for capture device names, because
387              * VLC does not want to open them when using default instead of hw.
388              * plughw also does not work.
389              *
390              * TODO investigate what happens
391              */
392             if (deviceName.startsWith(QLatin1String("default"))) {
393                 deviceName.replace(0, 7, "hw");
394             }
395             if (deviceName.startsWith(QLatin1String("plughw"))) {
396                 deviceName.replace(0, 6, "hw");
397             }
398             if (deviceName.startsWith(QLatin1String("x-phonon"))) {
399                 deviceName.replace(0, 8, "hw");
400             }
401 
402             loadMedia(QLatin1Literal("alsa://") % deviceName);
403         } else if (driverName == "screen") {
404             loadMedia(QLatin1Literal("screen://") % deviceName);
405 
406             // Set the isScreen flag needed to add extra options in playInternal
407             m_isScreen = true;
408         } else {
409             error() << Q_FUNC_INFO << "Unsupported MediaSource::CaptureDevice:" << driverName;
410             break;
411         }
412         break;
413     }
414     case MediaSource::Stream:
415         m_streamReader = new StreamReader(this);
416         // LibVLC refuses to emit seekability as it does a try-and-seek approach
417         // to work around this we exchange the player's seekability signal
418         // for the readers
419         // https://bugs.kde.org/show_bug.cgi?id=293012
420         connect(m_streamReader, SIGNAL(streamSeekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
421         disconnect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
422         // Only connect now to avoid seekability detection before we are connected.
423         m_streamReader->connectToSource(source);
424         loadMedia(QByteArray("imem://"));
425         break;
426     }
427 
428     debug() << "Sending currentSourceChanged";
429     emit currentSourceChanged(m_mediaSource);
430 }
431 
setNextSource(const MediaSource & source)432 void MediaObject::setNextSource(const MediaSource &source)
433 {
434     DEBUG_BLOCK;
435     debug() << source.url();
436     m_nextSource = source;
437     // This function is not ever called by the consumer but only libphonon.
438     // Furthermore libphonon only calls this function in its aboutToFinish slot,
439     // iff sources are already in the queue. In case our aboutToFinish was too
440     // late we may already be stopped when the slot gets activated.
441     // Therefore we need to make sure that we move to the next source iff
442     // this function is called when we are in stoppedstate.
443     if (m_state == StoppedState)
444         moveToNext();
445 }
446 
prefinishMark() const447 qint32 MediaObject::prefinishMark() const
448 {
449     return m_prefinishMark;
450 }
451 
setPrefinishMark(qint32 msecToEnd)452 void MediaObject::setPrefinishMark(qint32 msecToEnd)
453 {
454     m_prefinishMark = msecToEnd;
455     if (currentTime() < totalTime() - m_prefinishMark) {
456         // Not about to finish
457         m_prefinishEmitted = false;
458     }
459 }
460 
transitionTime() const461 qint32 MediaObject::transitionTime() const
462 {
463     return m_transitionTime;
464 }
465 
setTransitionTime(qint32 time)466 void MediaObject::setTransitionTime(qint32 time)
467 {
468     m_transitionTime = time;
469 }
470 
emitAboutToFinish()471 void MediaObject::emitAboutToFinish()
472 {
473     if (!m_aboutToFinishEmitted) {
474         // Track is about to finish
475         m_aboutToFinishEmitted = true;
476         emit aboutToFinish();
477     }
478 }
479 
480 // State changes are force queued by libphonon.
changeState(Phonon::State newState)481 void MediaObject::changeState(Phonon::State newState)
482 {
483     DEBUG_BLOCK;
484 
485     // State not changed
486     if (newState == m_state)
487         return;
488 
489     debug() << m_state << "-->" << newState;
490 
491 #ifdef __GNUC__
492 #warning do we actually need m_seekpoint? if a consumer seeks before playing state that is their problem?!
493 #endif
494     // Workaround that seeking needs to work before the file is being played...
495     // We store seeks and apply them when going to seek (or discard them on reset).
496     if (newState == PlayingState) {
497         if (m_seekpoint != 0) {
498             seek(m_seekpoint);
499             m_seekpoint = 0;
500         }
501     }
502 
503     // State changed
504     Phonon::State previousState = m_state;
505     m_state = newState;
506     emit stateChanged(m_state, previousState);
507 }
508 
moveToNextSource()509 void MediaObject::moveToNextSource()
510 {
511     DEBUG_BLOCK;
512 
513     setSource(m_nextSource);
514 
515     // The consumer may set an invalid source as final source to force a
516     // queued stop, regardless of how fast the consumer is at actually calling
517     // stop. Such a source must not cause an actual move (moving ~= state
518     // changes towards playing) but instead we only set the source to reflect
519     // that we got the setNextSource call.
520     if (hasNextTrack())
521         play();
522 
523     m_nextSource = MediaSource(QUrl());
524 }
525 
hasNextTrack()526 inline bool MediaObject::hasNextTrack()
527 {
528     return m_nextSource.type() != MediaSource::Invalid && m_nextSource.type() != MediaSource::Empty;
529 }
530 
unloadMedia()531 inline void MediaObject::unloadMedia()
532 {
533     if (m_media) {
534         m_media->disconnect(this);
535         m_media->deleteLater();
536         m_media = 0;
537     }
538 }
539 
setupMedia()540 void MediaObject::setupMedia()
541 {
542     DEBUG_BLOCK;
543 
544     unloadMedia();
545     resetMembers();
546 
547     // Create a media with the given MRL
548     m_media = new Media(m_mrl, this);
549     if (!m_media)
550         error() << "libVLC:" << LibVLC::errorMessage();
551 
552     if (m_isScreen) {
553         m_media->addOption(QLatin1String("screen-fps=24.0"));
554         m_media->addOption(QLatin1String("screen-caching=300"));
555     }
556 
557     if (source().discType() == Cd && m_currentTitle > 0)
558         m_media->setCdTrack(m_currentTitle);
559 
560     if (m_streamReader)
561         // StreamReader is no sink but a source, for this we have no concept right now
562         // also we do not need one since the reader is the only source we have.
563         // Consequently we need to manually tell the StreamReader to attach to the Media.
564         m_streamReader->addToMedia(m_media);
565 
566     if (!m_subtitleAutodetect)
567         m_media->addOption(QLatin1String(":no-sub-autodetect-file"));
568 
569     if (m_subtitleEncoding != QLatin1String("UTF-8")) // utf8 is phonon default, so let vlc handle it
570         m_media->addOption(QLatin1String(":subsdec-encoding="), m_subtitleEncoding);
571 
572     if (!m_subtitleFontChanged) // Update font settings
573         m_subtitleFont = QFont();
574 
575 #ifdef __GNUC__
576 #warning freetype module is not working as expected - font api not working
577 #endif
578     // BUG: VLC's freetype module doesn't pick up per-media options
579     // vlc -vvvv --freetype-font="Comic Sans MS" multiple_sub_sample.mkv :freetype-font=Arial
580     // https://trac.videolan.org/vlc/ticket/9797
581     m_media->addOption(QLatin1String(":freetype-font="), m_subtitleFont.family());
582     m_media->addOption(QLatin1String(":freetype-fontsize="), m_subtitleFont.pointSize());
583     if (m_subtitleFont.bold())
584         m_media->addOption(QLatin1String(":freetype-bold"));
585     else
586         m_media->addOption(QLatin1String(":no-freetype-bold"));
587 
588     foreach (SinkNode *sink, m_sinks) {
589         sink->addToMedia(m_media);
590     }
591 
592     // Connect to Media signals. Disconnection is done at unloading.
593     connect(m_media, SIGNAL(durationChanged(qint64)),
594             this, SLOT(updateDuration(qint64)));
595     connect(m_media, SIGNAL(metaDataChanged()),
596             this, SLOT(updateMetaData()));
597 
598     // Update available audio channels/subtitles/angles/chapters/etc...
599     // i.e everything from MediaController
600     // There is no audio channel/subtitle/angle/chapter events inside libvlc
601     // so let's send our own events...
602     // This will reset the GUI
603     resetMediaController();
604 
605     // Play
606     m_player->setMedia(m_media);
607 }
608 
errorString() const609 QString MediaObject::errorString() const
610 {
611     return libvlc_errmsg();
612 }
613 
hasVideo() const614 bool MediaObject::hasVideo() const
615 {
616     // Cached: sometimes 4.0.0-dev sends the vout event but then
617     // has_vout is still false. Guard against this by simply always reporting
618     // the last hasVideoChanged value. If that is off we can still drop into
619     // libvlc in case it changed meanwhile.
620     return m_hasVideo || m_player->hasVideoOutput();
621 }
622 
isSeekable() const623 bool MediaObject::isSeekable() const
624 {
625     if (m_streamReader)
626         return m_streamReader->streamSeekable();
627     return m_player->isSeekable();
628 }
629 
updateDuration(qint64 newDuration)630 void MediaObject::updateDuration(qint64 newDuration)
631 {
632     // This here cache is needed because we need to provide -1 as totalTime()
633     // for as long as we do not get a proper update through this slot.
634     // VLC reports -1 with no media but 0 if it does not know the duration, so
635     // apps that assume 0 = unknown get screwed if they query too early.
636     // http://bugs.tomahawk-player.org/browse/TWK-1029
637     m_totalTime = newDuration;
638     emit totalTimeChanged(m_totalTime);
639 }
640 
updateMetaData()641 void MediaObject::updateMetaData()
642 {
643     QMultiMap<QString, QString> metaDataMap;
644 
645     const QString artist = m_media->meta(libvlc_meta_Artist);
646     const QString title = m_media->meta(libvlc_meta_Title);
647     const QString nowPlaying = m_media->meta(libvlc_meta_NowPlaying);
648 
649     // Streams sometimes have the artist and title munged in nowplaying.
650     // With ALBUM = Title and TITLE = NowPlaying it will still show up nicely in Amarok.
651     if (artist.isEmpty() && !nowPlaying.isEmpty()) {
652         metaDataMap.insert(QLatin1String("ALBUM"), title);
653         metaDataMap.insert(QLatin1String("TITLE"), nowPlaying);
654     } else {
655         metaDataMap.insert(QLatin1String("ALBUM"), m_media->meta(libvlc_meta_Album));
656         metaDataMap.insert(QLatin1String("TITLE"), title);
657     }
658 
659     metaDataMap.insert(QLatin1String("ARTIST"), artist);
660     metaDataMap.insert(QLatin1String("DATE"), m_media->meta(libvlc_meta_Date));
661     metaDataMap.insert(QLatin1String("GENRE"), m_media->meta(libvlc_meta_Genre));
662     metaDataMap.insert(QLatin1String("TRACKNUMBER"), m_media->meta(libvlc_meta_TrackNumber));
663     metaDataMap.insert(QLatin1String("DESCRIPTION"), m_media->meta(libvlc_meta_Description));
664     metaDataMap.insert(QLatin1String("COPYRIGHT"), m_media->meta(libvlc_meta_Copyright));
665     metaDataMap.insert(QLatin1String("URL"), m_media->meta(libvlc_meta_URL));
666     metaDataMap.insert(QLatin1String("ENCODEDBY"), m_media->meta(libvlc_meta_EncodedBy));
667 
668     if (metaDataMap == m_vlcMetaData) {
669         // No need to issue any change, the data is the same
670         return;
671     }
672     m_vlcMetaData = metaDataMap;
673 
674     emit metaDataChanged(metaDataMap);
675 }
676 
updateState(MediaPlayer::State state)677 void MediaObject::updateState(MediaPlayer::State state)
678 {
679     DEBUG_BLOCK;
680     debug() << state;
681     debug() << "attempted autoplay?" << m_attemptingAutoplay;
682 
683     if (m_attemptingAutoplay) {
684         switch (state) {
685         case MediaPlayer::PlayingState:
686         case MediaPlayer::PausedState:
687             m_attemptingAutoplay = false;
688             break;
689         case MediaPlayer::ErrorState:
690             debug() << "autoplay failed, must be end of media.";
691             // The error should not be reflected to the consumer. So we swap it
692             // for finished() which is actually what is happening here.
693             // Or so we think ;)
694             state = MediaPlayer::EndedState;
695             --m_currentTitle;
696             break;
697         default:
698             debug() << "not handling as part of autplay:" << state;
699             break;
700         }
701     }
702 
703     switch (state) {
704     case MediaPlayer::NoState:
705         changeState(LoadingState);
706         break;
707     case MediaPlayer::OpeningState:
708         changeState(LoadingState);
709         break;
710     case MediaPlayer::BufferingState:
711         changeState(BufferingState);
712         break;
713     case MediaPlayer::PlayingState:
714         changeState(PlayingState);
715         break;
716     case MediaPlayer::PausedState:
717         changeState(PausedState);
718         break;
719     case MediaPlayer::StoppedState:
720         changeState(StoppedState);
721         break;
722     case MediaPlayer::EndedState:
723         if (hasNextTrack()) {
724             moveToNextSource();
725         } else if (source().discType() == Cd && m_autoPlayTitles && !m_attemptingAutoplay) {
726             debug() << "trying to simulate autoplay";
727             m_attemptingAutoplay = true;
728             m_player->setCdTrack(++m_currentTitle);
729         } else {
730             m_attemptingAutoplay = false;
731             emitAboutToFinish();
732             emit finished();
733             changeState(StoppedState);
734         }
735         break;
736     case MediaPlayer::ErrorState:
737         debug() << errorString();
738         emitAboutToFinish();
739         emit finished();
740         changeState(ErrorState);
741         break;
742     }
743 
744     if (m_buffering) {
745         switch (state) {
746         case MediaPlayer::BufferingState:
747             break;
748         case MediaPlayer::PlayingState:
749             debug() << "Restoring buffering state after state change to Playing";
750             changeState(BufferingState);
751             m_stateAfterBuffering = PlayingState;
752             break;
753         case MediaPlayer::PausedState:
754             debug() << "Restoring buffering state after state change to Paused";
755             changeState(BufferingState);
756             m_stateAfterBuffering = PausedState;
757             break;
758         default:
759             debug() << "Buffering aborted!";
760             m_buffering = false;
761             break;
762         }
763     }
764 
765     return;
766 }
767 
onHasVideoChanged(bool hasVideo)768 void MediaObject::onHasVideoChanged(bool hasVideo)
769 {
770     DEBUG_BLOCK;
771     if (m_hasVideo != hasVideo) {
772         m_hasVideo = hasVideo;
773         emit hasVideoChanged(m_hasVideo);
774     } else {
775         // We can simply return if we are have the appropriate caching already.
776         // Otherwise we'd do pointless rescans of mediacontroller stuff.
777         // MC and MO are force-reset on media changes anyway.
778         return;
779     }
780 
781     refreshDescriptors();
782 }
783 
setBufferStatus(int percent)784 void MediaObject::setBufferStatus(int percent)
785 {
786     // VLC does not have a buffering state (surprise!) but instead only sends the
787     // event (surprise!). Hence we need to simulate the state change.
788     // Problem with BufferingState is that it is actually concurrent to Playing or Paused
789     // meaning that while you are buffering you can also pause, thus triggering
790     // a state change to paused. To handle this we let updateState change the
791     // state accordingly (as we need to allow the UI to update itself, and
792     // immediately after that we change back to buffering again.
793     // This loop can only be interrupted by a state change to !Playing & !Paused
794     // or by reaching a 100 % buffer caching (i.e. full cache).
795 
796     m_buffering = true;
797     if (m_state != BufferingState) {
798         m_stateAfterBuffering = m_state;
799         changeState(BufferingState);
800     }
801 
802     emit bufferStatus(percent);
803 
804     // Transit to actual state only after emission so the signal is still
805     // delivered while in BufferingState.
806     if (percent >= 100) { // http://trac.videolan.org/vlc/ticket/5277
807         m_buffering = false;
808         changeState(m_stateAfterBuffering);
809     }
810 }
811 
refreshDescriptors()812 void MediaObject::refreshDescriptors()
813 {
814     if (m_player->titleCount() > 0)
815         refreshTitles();
816 
817     if (hasVideo()) {
818         refreshAudioChannels();
819         refreshSubtitles();
820 
821         if (m_player->videoChapterCount() > 0)
822             refreshChapters(m_player->title());
823     }
824 }
825 
totalTime() const826 qint64 MediaObject::totalTime() const
827 {
828     return m_totalTime;
829 }
830 
addSink(SinkNode * node)831 void MediaObject::addSink(SinkNode *node)
832 {
833     Q_ASSERT(!m_sinks.contains(node));
834     m_sinks.append(node);
835 }
836 
removeSink(SinkNode * node)837 void MediaObject::removeSink(SinkNode *node)
838 {
839     Q_ASSERT(node);
840     m_sinks.removeAll(node);
841 }
842 
843 } // namespace VLC
844 } // namespace Phonon
845