File indexing completed on 2024-12-01 04:21:26

0001 /*
0002     Copyright (C) 2007-2008 Tanguy Krotoff <tkrotoff@gmail.com>
0003     Copyright (C) 2008 Lukas Durfina <lukas.durfina@gmail.com>
0004     Copyright (C) 2009 Fathi Boudra <fabo@kde.org>
0005     Copyright (C) 2010 Ben Cooksley <sourtooth@gmail.com>
0006     Copyright (C) 2009-2011 vlc-phonon AUTHORS <kde-multimedia@kde.org>
0007     Copyright (C) 2010-2021 Harald Sitter <sitter@kde.org>
0008 
0009     This library is free software; you can redistribute it and/or
0010     modify it under the terms of the GNU Lesser General Public
0011     License as published by the Free Software Foundation; either
0012     version 2.1 of the License, or (at your option) any later version.
0013 
0014     This library is distributed in the hope that it will be useful,
0015     but WITHOUT ANY WARRANTY; without even the implied warranty of
0016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0017     Lesser General Public License for more details.
0018 
0019     You should have received a copy of the GNU Lesser General Public
0020     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "mediaobject.h"
0024 
0025 #include <QtCore/QDir>
0026 #include <QtCore/QStringBuilder>
0027 #include <QtCore/QUrl>
0028 
0029 #include <phonon/pulsesupport.h>
0030 
0031 #include <vlc/libvlc_version.h>
0032 #include <vlc/vlc.h>
0033 
0034 #include "utils/debug.h"
0035 #include "utils/libvlc.h"
0036 #include "media.h"
0037 #include "sinknode.h"
0038 #include "streamreader.h"
0039 
0040 //Time in milliseconds before sending aboutToFinish() signal
0041 //2 seconds
0042 static const int ABOUT_TO_FINISH_TIME = 2000;
0043 
0044 namespace Phonon {
0045 namespace VLC {
0046 
0047 MediaObject::MediaObject(QObject *parent)
0048     : QObject(parent)
0049     , m_nextSource(MediaSource(QUrl()))
0050     , m_streamReader(0)
0051     , m_state(Phonon::StoppedState)
0052     , m_tickInterval(0)
0053     , m_transitionTime(0)
0054     , m_media(0)
0055 {
0056     qRegisterMetaType<QMultiMap<QString, QString> >("QMultiMap<QString, QString>");
0057 
0058     m_player = new MediaPlayer(this);
0059     Q_ASSERT(m_player);
0060     if (!m_player->libvlc_media_player())
0061         error() << "libVLC:" << LibVLC::errorMessage();
0062 
0063     // Player signals.
0064     connect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
0065     connect(m_player, SIGNAL(timeChanged(qint64)), this, SLOT(timeChanged(qint64)));
0066     connect(m_player, SIGNAL(stateChanged(MediaPlayer::State)), this, SLOT(updateState(MediaPlayer::State)));
0067     connect(m_player, SIGNAL(hasVideoChanged(bool)), this, SLOT(onHasVideoChanged(bool)));
0068     connect(m_player, SIGNAL(bufferChanged(int)), this, SLOT(setBufferStatus(int)));
0069     connect(m_player, SIGNAL(timeChanged(qint64)), this, SLOT(timeChanged(qint64)));
0070 
0071     // Internal Signals.
0072     connect(this, SIGNAL(moveToNext()), SLOT(moveToNextSource()));
0073     connect(m_refreshTimer, SIGNAL(timeout()), this, SLOT(refreshDescriptors()));
0074 
0075     resetMembers();
0076 }
0077 
0078 MediaObject::~MediaObject()
0079 {
0080     unloadMedia();
0081     // Shutdown the pulseaudio mainloop before the MediaPlayer gets destroyed
0082     // (it is a child of the MO). There appears to be a peculiar race condition
0083     // between the pa_thread_mainloop used by VLC and the pa_glib_mainloop used
0084     // by Phonon's PulseSupport where for a very short time frame after the
0085     // former was stopped and freed the latter can run and fall over
0086     //   Invalid read from eventfd: Bad file descriptor
0087     //   Code should not be reached at pulsecore/fdsem.c:157, function flush(). Aborting.
0088     // Since we don't use PulseSupport since VLC 2.2 we can simply force a
0089     // loop shutdown even when the application isn't about to terminate.
0090     // The instance gets created again anyway.
0091     PulseSupport::shutdown();
0092 }
0093 
0094 void MediaObject::resetMembers()
0095 {
0096     // default to -1, so that streams won't break and to comply with the docs (-1 if unknown)
0097     m_totalTime = -1;
0098     m_hasVideo = false;
0099     m_seekpoint = 0;
0100 
0101     m_prefinishEmitted = false;
0102     m_aboutToFinishEmitted = false;
0103 
0104     m_lastTick = 0;
0105 
0106     m_timesVideoChecked = 0;
0107 
0108     m_buffering = false;
0109     m_stateAfterBuffering = ErrorState;
0110 
0111     resetMediaController();
0112 
0113     // Forcefully shutdown plusesupport to prevent crashing between the PS PA glib mainloop
0114     // and the VLC PA threaded mainloop. See destructor.
0115     PulseSupport::shutdown();
0116 }
0117 
0118 void MediaObject::play()
0119 {
0120     DEBUG_BLOCK;
0121 
0122     switch (m_state) {
0123     case PlayingState:
0124         // Do not do anything if we are already playing (as per documentation).
0125         return;
0126     case PausedState:
0127         m_player->resume();
0128         break;
0129     default:
0130         setupMedia();
0131         if (m_player->play())
0132             error() << "libVLC:" << LibVLC::errorMessage();
0133         break;
0134     }
0135 }
0136 
0137 void MediaObject::pause()
0138 {
0139     DEBUG_BLOCK;
0140     switch (m_state) {
0141     case BufferingState:
0142     case PlayingState:
0143         m_player->pause();
0144         break;
0145     case PausedState:
0146         return;
0147     default:
0148         debug() << "doing paused play";
0149         setupMedia();
0150         m_player->pausedPlay();
0151         break;
0152     }
0153 }
0154 
0155 void MediaObject::stop()
0156 {
0157     DEBUG_BLOCK;
0158     if (m_streamReader)
0159         m_streamReader->unlock();
0160     m_nextSource = MediaSource(QUrl());
0161     m_player->stop();
0162 }
0163 
0164 void MediaObject::seek(qint64 milliseconds)
0165 {
0166     DEBUG_BLOCK;
0167 
0168     switch (m_state) {
0169     case PlayingState:
0170     case PausedState:
0171     case BufferingState:
0172         break;
0173     default:
0174         // Seeking while not being in a playingish state is cached for later.
0175         m_seekpoint = milliseconds;
0176         return;
0177     }
0178 
0179     debug() << "seeking" << milliseconds << "msec";
0180 
0181     m_player->setTime(milliseconds);
0182 
0183     const qint64 time = currentTime();
0184     const qint64 total = totalTime();
0185 
0186     // Reset last tick marker so we emit time even after seeking
0187     if (time < m_lastTick)
0188         m_lastTick = time;
0189     if (time < total - m_prefinishMark)
0190         m_prefinishEmitted = false;
0191     if (time < total - ABOUT_TO_FINISH_TIME)
0192         m_aboutToFinishEmitted = false;
0193 }
0194 
0195 void MediaObject::timeChanged(qint64 time)
0196 {
0197     const qint64 totalTime = m_totalTime;
0198 
0199     switch (m_state) {
0200     case PlayingState:
0201     case BufferingState:
0202     case PausedState:
0203         emitTick(time);
0204     default:
0205         break;
0206     }
0207 
0208     if (m_state == PlayingState || m_state == BufferingState) { // Buffering is concurrent
0209         if (time >= totalTime - m_prefinishMark) {
0210             if (!m_prefinishEmitted) {
0211                 m_prefinishEmitted = true;
0212                 emit prefinishMarkReached(totalTime - time);
0213             }
0214         }
0215         // Note that when the totalTime is <= 0 we cannot calculate any sane delta.
0216         if (totalTime > 0 && time >= totalTime - ABOUT_TO_FINISH_TIME)
0217             emitAboutToFinish();
0218     }
0219 }
0220 
0221 void MediaObject::emitTick(qint64 time)
0222 {
0223     if (m_tickInterval == 0) // Make sure we do not ever emit ticks when deactivated.\]
0224         return;
0225     if (time + m_tickInterval >= m_lastTick) {
0226         m_lastTick = time;
0227         emit tick(time);
0228     }
0229 }
0230 
0231 void MediaObject::loadMedia(const QByteArray &mrl)
0232 {
0233     DEBUG_BLOCK;
0234 
0235     // Initial state is loading, from which we quickly progress to stopped because
0236     // libvlc does not provide feedback on loading and the media does not get loaded
0237     // until we play it.
0238     // FIXME: libvlc should really allow for this as it can cause unexpected delay
0239     // even though the GUI might indicate that playback should start right away.
0240     changeState(Phonon::LoadingState);
0241 
0242     m_mrl = mrl;
0243     debug() << "loading encoded:" << m_mrl;
0244 
0245     // We do not have a loading state generally speaking, usually the backend
0246     // is expected to go to loading state and then at some point reach stopped,
0247     // at which point playback can be started.
0248     // See state enum documentation for more information.
0249     changeState(Phonon::StoppedState);
0250 }
0251 
0252 void MediaObject::loadMedia(const QString &mrl)
0253 {
0254     loadMedia(mrl.toUtf8());
0255 }
0256 
0257 qint32 MediaObject::tickInterval() const
0258 {
0259     return m_tickInterval;
0260 }
0261 
0262 /**
0263  * Supports runtime changes.
0264  * If the user goes to tick(0) we stop the timer, otherwise we fire it up.
0265  */
0266 void MediaObject::setTickInterval(qint32 interval)
0267 {
0268     m_tickInterval = interval;
0269 }
0270 
0271 qint64 MediaObject::currentTime() const
0272 {
0273     qint64 time = -1;
0274 
0275     switch (state()) {
0276     case Phonon::PausedState:
0277     case Phonon::BufferingState:
0278     case Phonon::PlayingState:
0279         time = m_player->time();
0280         break;
0281     case Phonon::StoppedState:
0282     case Phonon::LoadingState:
0283         time = 0;
0284         break;
0285     case Phonon::ErrorState:
0286         time = -1;
0287         break;
0288     }
0289 
0290     return time;
0291 }
0292 
0293 Phonon::State MediaObject::state() const
0294 {
0295     return m_state;
0296 }
0297 
0298 Phonon::ErrorType MediaObject::errorType() const
0299 {
0300     return Phonon::NormalError;
0301 }
0302 
0303 MediaSource MediaObject::source() const
0304 {
0305     return m_mediaSource;
0306 }
0307 
0308 void MediaObject::setSource(const MediaSource &source)
0309 {
0310     DEBUG_BLOCK;
0311 
0312     // Reset previous streamereaders
0313     if (m_streamReader) {
0314         m_streamReader->unlock();
0315         delete m_streamReader;
0316         m_streamReader = 0;
0317         // For streamreaders we exchange the player's seekability with the
0318         // reader's so here we change it back.
0319         // Note: the reader auto-disconnects due to destruction.
0320         connect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
0321     }
0322 
0323     // Reset previous isScreen flag
0324     m_isScreen = false;
0325 
0326     m_mediaSource = source;
0327 
0328     QByteArray url;
0329     switch (source.type()) {
0330     case MediaSource::Invalid:
0331         error() << Q_FUNC_INFO << "MediaSource Type is Invalid:" << source.type();
0332         break;
0333     case MediaSource::Empty:
0334         error() << Q_FUNC_INFO << "MediaSource is empty.";
0335         break;
0336     case MediaSource::LocalFile:
0337     case MediaSource::Url:
0338         debug() << "MediaSource::Url:" << source.url();
0339         if (source.url().scheme().isEmpty()) {
0340             url = "file://";
0341             // QUrl considers url.scheme.isEmpty() == url.isRelative(),
0342             // so to be sure the url is not actually absolute we just
0343             // check the first character
0344             if (!source.url().toString().startsWith('/'))
0345                 url.append(QFile::encodeName(QDir::currentPath()) + '/');
0346         }
0347         url += source.url().toEncoded();
0348         loadMedia(url);
0349         break;
0350     case MediaSource::Disc:
0351         switch (source.discType()) {
0352         case Phonon::NoDisc:
0353             error() << Q_FUNC_INFO << "the MediaSource::Disc doesn't specify which one (Phonon::NoDisc)";
0354             return;
0355         case Phonon::Cd:
0356             loadMedia(QStringLiteral("cdda://") % m_mediaSource.deviceName());
0357             break;
0358         case Phonon::Dvd:
0359             loadMedia(QStringLiteral("dvd://") % m_mediaSource.deviceName());
0360             break;
0361         case Phonon::Vcd:
0362             loadMedia(QStringLiteral("vcd://") % m_mediaSource.deviceName());
0363             break;
0364         case Phonon::BluRay:
0365             loadMedia(QStringLiteral("bluray://") % m_mediaSource.deviceName());
0366             break;
0367         }
0368         break;
0369     case MediaSource::CaptureDevice: {
0370         QByteArray driverName;
0371         QString deviceName;
0372 
0373         if (source.deviceAccessList().isEmpty()) {
0374             error() << Q_FUNC_INFO << "No device access list for this capture device";
0375             break;
0376         }
0377 
0378         // TODO try every device in the access list until it works, not just the first one
0379         driverName = source.deviceAccessList().first().first;
0380         deviceName = source.deviceAccessList().first().second;
0381 
0382         if (driverName == QByteArray("v4l2")) {
0383             loadMedia(QStringLiteral("v4l2://") % deviceName);
0384         } else if (driverName == QByteArray("alsa")) {
0385             /*
0386              * Replace "default" and "plughw" and "x-phonon" with "hw" for capture device names, because
0387              * VLC does not want to open them when using default instead of hw.
0388              * plughw also does not work.
0389              *
0390              * TODO investigate what happens
0391              */
0392             if (deviceName.startsWith(QLatin1String("default"))) {
0393                 deviceName.replace(0, 7, "hw");
0394             }
0395             if (deviceName.startsWith(QLatin1String("plughw"))) {
0396                 deviceName.replace(0, 6, "hw");
0397             }
0398             if (deviceName.startsWith(QLatin1String("x-phonon"))) {
0399                 deviceName.replace(0, 8, "hw");
0400             }
0401 
0402             loadMedia(QStringLiteral("alsa://") % deviceName);
0403         } else if (driverName == "screen") {
0404             loadMedia(QStringLiteral("screen://") % deviceName);
0405 
0406             // Set the isScreen flag needed to add extra options in playInternal
0407             m_isScreen = true;
0408         } else {
0409             error() << Q_FUNC_INFO << "Unsupported MediaSource::CaptureDevice:" << driverName;
0410             break;
0411         }
0412         break;
0413     }
0414     case MediaSource::Stream:
0415         m_streamReader = new StreamReader(this);
0416         // LibVLC refuses to emit seekability as it does a try-and-seek approach
0417         // to work around this we exchange the player's seekability signal
0418         // for the readers
0419         // https://bugs.kde.org/show_bug.cgi?id=293012
0420         connect(m_streamReader, SIGNAL(streamSeekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
0421         disconnect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool)));
0422         // Only connect now to avoid seekability detection before we are connected.
0423         m_streamReader->connectToSource(source);
0424         loadMedia(QByteArray("imem://"));
0425         break;
0426     }
0427 
0428     debug() << "Sending currentSourceChanged";
0429     emit currentSourceChanged(m_mediaSource);
0430 }
0431 
0432 void MediaObject::setNextSource(const MediaSource &source)
0433 {
0434     DEBUG_BLOCK;
0435     debug() << source.url();
0436     m_nextSource = source;
0437     // This function is not ever called by the consumer but only libphonon.
0438     // Furthermore libphonon only calls this function in its aboutToFinish slot,
0439     // iff sources are already in the queue. In case our aboutToFinish was too
0440     // late we may already be stopped when the slot gets activated.
0441     // Therefore we need to make sure that we move to the next source iff
0442     // this function is called when we are in stoppedstate.
0443     if (m_state == StoppedState)
0444         moveToNext();
0445 }
0446 
0447 qint32 MediaObject::prefinishMark() const
0448 {
0449     return m_prefinishMark;
0450 }
0451 
0452 void MediaObject::setPrefinishMark(qint32 msecToEnd)
0453 {
0454     m_prefinishMark = msecToEnd;
0455     if (currentTime() < totalTime() - m_prefinishMark) {
0456         // Not about to finish
0457         m_prefinishEmitted = false;
0458     }
0459 }
0460 
0461 qint32 MediaObject::transitionTime() const
0462 {
0463     return m_transitionTime;
0464 }
0465 
0466 void MediaObject::setTransitionTime(qint32 time)
0467 {
0468     m_transitionTime = time;
0469 }
0470 
0471 void MediaObject::emitAboutToFinish()
0472 {
0473     if (!m_aboutToFinishEmitted) {
0474         // Track is about to finish
0475         m_aboutToFinishEmitted = true;
0476         emit aboutToFinish();
0477     }
0478 }
0479 
0480 // State changes are force queued by libphonon.
0481 void MediaObject::changeState(Phonon::State newState)
0482 {
0483     DEBUG_BLOCK;
0484 
0485     // State not changed
0486     if (newState == m_state)
0487         return;
0488 
0489     debug() << m_state << "-->" << newState;
0490 
0491 #ifdef __GNUC__
0492 #warning do we actually need m_seekpoint? if a consumer seeks before playing state that is their problem?!
0493 #endif
0494     // Workaround that seeking needs to work before the file is being played...
0495     // We store seeks and apply them when going to seek (or discard them on reset).
0496     if (newState == PlayingState) {
0497         if (m_seekpoint != 0) {
0498             seek(m_seekpoint);
0499             m_seekpoint = 0;
0500         }
0501     }
0502 
0503     // State changed
0504     Phonon::State previousState = m_state;
0505     m_state = newState;
0506     emit stateChanged(m_state, previousState);
0507 }
0508 
0509 void MediaObject::moveToNextSource()
0510 {
0511     DEBUG_BLOCK;
0512 
0513     setSource(m_nextSource);
0514 
0515     // The consumer may set an invalid source as final source to force a
0516     // queued stop, regardless of how fast the consumer is at actually calling
0517     // stop. Such a source must not cause an actual move (moving ~= state
0518     // changes towards playing) but instead we only set the source to reflect
0519     // that we got the setNextSource call.
0520     if (hasNextTrack())
0521         play();
0522 
0523     m_nextSource = MediaSource(QUrl());
0524 }
0525 
0526 inline bool MediaObject::hasNextTrack()
0527 {
0528     return m_nextSource.type() != MediaSource::Invalid && m_nextSource.type() != MediaSource::Empty;
0529 }
0530 
0531 inline void MediaObject::unloadMedia()
0532 {
0533     if (m_media) {
0534         m_media->disconnect(this);
0535         m_media->deleteLater();
0536         m_media = 0;
0537     }
0538 }
0539 
0540 void MediaObject::setupMedia()
0541 {
0542     DEBUG_BLOCK;
0543 
0544     unloadMedia();
0545     resetMembers();
0546 
0547     // Create a media with the given MRL
0548     m_media = new Media(m_mrl, this);
0549 
0550     if (m_isScreen) {
0551         m_media->addOption(QLatin1String("screen-fps=24.0"));
0552         m_media->addOption(QLatin1String("screen-caching=300"));
0553     }
0554 
0555     if (source().discType() == Cd && m_currentTitle > 0)
0556         m_media->setCdTrack(m_currentTitle);
0557 
0558     if (m_streamReader)
0559         // StreamReader is no sink but a source, for this we have no concept right now
0560         // also we do not need one since the reader is the only source we have.
0561         // Consequently we need to manually tell the StreamReader to attach to the Media.
0562         m_streamReader->addToMedia(m_media);
0563 
0564     if (!m_subtitleAutodetect)
0565         m_media->addOption(QLatin1String(":no-sub-autodetect-file"));
0566 
0567     if (m_subtitleEncoding != QLatin1String("UTF-8")) // utf8 is phonon default, so let vlc handle it
0568         m_media->addOption(QLatin1String(":subsdec-encoding="), m_subtitleEncoding);
0569 
0570     if (!m_subtitleFontChanged) // Update font settings
0571         m_subtitleFont = QFont();
0572 
0573 #ifdef __GNUC__
0574 #warning freetype module is not working as expected - font api not working
0575 #endif
0576     // BUG: VLC's freetype module doesn't pick up per-media options
0577     // vlc -vvvv --freetype-font="Comic Sans MS" multiple_sub_sample.mkv :freetype-font=Arial
0578     // https://trac.videolan.org/vlc/ticket/9797
0579     m_media->addOption(QLatin1String(":freetype-font="), m_subtitleFont.family());
0580     m_media->addOption(QLatin1String(":freetype-fontsize="), m_subtitleFont.pointSize());
0581     if (m_subtitleFont.bold())
0582         m_media->addOption(QLatin1String(":freetype-bold"));
0583     else
0584         m_media->addOption(QLatin1String(":no-freetype-bold"));
0585 
0586     foreach (SinkNode *sink, m_sinks) {
0587         sink->addToMedia(m_media);
0588     }
0589 
0590     // Connect to Media signals. Disconnection is done at unloading.
0591     connect(m_media, SIGNAL(durationChanged(qint64)),
0592             this, SLOT(updateDuration(qint64)));
0593     connect(m_media, SIGNAL(metaDataChanged()),
0594             this, SLOT(updateMetaData()));
0595 
0596     // Update available audio channels/subtitles/angles/chapters/etc...
0597     // i.e everything from MediaController
0598     // There is no audio channel/subtitle/angle/chapter events inside libvlc
0599     // so let's send our own events...
0600     // This will reset the GUI
0601     resetMediaController();
0602 
0603     // Play
0604     m_player->setMedia(m_media);
0605 }
0606 
0607 QString MediaObject::errorString() const
0608 {
0609     return libvlc_errmsg();
0610 }
0611 
0612 bool MediaObject::hasVideo() const
0613 {
0614     // Cached: sometimes 4.0.0-dev sends the vout event but then
0615     // has_vout is still false. Guard against this by simply always reporting
0616     // the last hasVideoChanged value. If that is off we can still drop into
0617     // libvlc in case it changed meanwhile.
0618     return m_hasVideo || m_player->hasVideoOutput();
0619 }
0620 
0621 bool MediaObject::isSeekable() const
0622 {
0623     if (m_streamReader)
0624         return m_streamReader->streamSeekable();
0625     return m_player->isSeekable();
0626 }
0627 
0628 void MediaObject::updateDuration(qint64 newDuration)
0629 {
0630     // This here cache is needed because we need to provide -1 as totalTime()
0631     // for as long as we do not get a proper update through this slot.
0632     // VLC reports -1 with no media but 0 if it does not know the duration, so
0633     // apps that assume 0 = unknown get screwed if they query too early.
0634     // http://bugs.tomahawk-player.org/browse/TWK-1029
0635     m_totalTime = newDuration;
0636     emit totalTimeChanged(m_totalTime);
0637 }
0638 
0639 void MediaObject::updateMetaData()
0640 {
0641     QMultiMap<QString, QString> metaDataMap;
0642 
0643     const QString artist = m_media->meta(libvlc_meta_Artist);
0644     const QString title = m_media->meta(libvlc_meta_Title);
0645     const QString nowPlaying = m_media->meta(libvlc_meta_NowPlaying);
0646 
0647     // Streams sometimes have the artist and title munged in nowplaying.
0648     // With ALBUM = Title and TITLE = NowPlaying it will still show up nicely in Amarok.
0649     if (artist.isEmpty() && !nowPlaying.isEmpty()) {
0650         metaDataMap.insert(QLatin1String("ALBUM"), title);
0651         metaDataMap.insert(QLatin1String("TITLE"), nowPlaying);
0652     } else {
0653         metaDataMap.insert(QLatin1String("ALBUM"), m_media->meta(libvlc_meta_Album));
0654         metaDataMap.insert(QLatin1String("TITLE"), title);
0655     }
0656 
0657     metaDataMap.insert(QLatin1String("ARTIST"), artist);
0658     metaDataMap.insert(QLatin1String("DATE"), m_media->meta(libvlc_meta_Date));
0659     metaDataMap.insert(QLatin1String("GENRE"), m_media->meta(libvlc_meta_Genre));
0660     metaDataMap.insert(QLatin1String("TRACKNUMBER"), m_media->meta(libvlc_meta_TrackNumber));
0661     metaDataMap.insert(QLatin1String("DESCRIPTION"), m_media->meta(libvlc_meta_Description));
0662     metaDataMap.insert(QLatin1String("COPYRIGHT"), m_media->meta(libvlc_meta_Copyright));
0663     metaDataMap.insert(QLatin1String("URL"), m_media->meta(libvlc_meta_URL));
0664     metaDataMap.insert(QLatin1String("ENCODEDBY"), m_media->meta(libvlc_meta_EncodedBy));
0665 
0666     if (metaDataMap == m_vlcMetaData) {
0667         // No need to issue any change, the data is the same
0668         return;
0669     }
0670     m_vlcMetaData = metaDataMap;
0671 
0672     emit metaDataChanged(metaDataMap);
0673 }
0674 
0675 void MediaObject::updateState(MediaPlayer::State state)
0676 {
0677     DEBUG_BLOCK;
0678     debug() << state;
0679     debug() << "attempted autoplay?" << m_attemptingAutoplay;
0680 
0681     if (m_attemptingAutoplay) {
0682         switch (state) {
0683         case MediaPlayer::PlayingState:
0684         case MediaPlayer::PausedState:
0685             m_attemptingAutoplay = false;
0686             break;
0687         case MediaPlayer::ErrorState:
0688             debug() << "autoplay failed, must be end of media.";
0689             // The error should not be reflected to the consumer. So we swap it
0690             // for finished() which is actually what is happening here.
0691             // Or so we think ;)
0692             state = MediaPlayer::EndedState;
0693             --m_currentTitle;
0694             break;
0695         default:
0696             debug() << "not handling as part of autplay:" << state;
0697             break;
0698         }
0699     }
0700 
0701     switch (state) {
0702     case MediaPlayer::NoState:
0703         changeState(LoadingState);
0704         break;
0705     case MediaPlayer::OpeningState:
0706         changeState(LoadingState);
0707         break;
0708     case MediaPlayer::BufferingState:
0709         changeState(BufferingState);
0710         break;
0711     case MediaPlayer::PlayingState:
0712         changeState(PlayingState);
0713         break;
0714     case MediaPlayer::PausedState:
0715         changeState(PausedState);
0716         break;
0717     case MediaPlayer::StoppedState:
0718         changeState(StoppedState);
0719         break;
0720     case MediaPlayer::EndedState:
0721         if (hasNextTrack()) {
0722             moveToNextSource();
0723         } else if (source().discType() == Cd && m_autoPlayTitles && !m_attemptingAutoplay) {
0724             debug() << "trying to simulate autoplay";
0725             m_attemptingAutoplay = true;
0726             m_player->setCdTrack(++m_currentTitle);
0727         } else {
0728             m_attemptingAutoplay = false;
0729             emitAboutToFinish();
0730             emit finished();
0731             changeState(StoppedState);
0732         }
0733         break;
0734     case MediaPlayer::ErrorState:
0735         debug() << errorString();
0736         emitAboutToFinish();
0737         emit finished();
0738         changeState(ErrorState);
0739         break;
0740     }
0741 
0742     if (m_buffering) {
0743         switch (state) {
0744         case MediaPlayer::BufferingState:
0745             break;
0746         case MediaPlayer::PlayingState:
0747             debug() << "Restoring buffering state after state change to Playing";
0748             changeState(BufferingState);
0749             m_stateAfterBuffering = PlayingState;
0750             break;
0751         case MediaPlayer::PausedState:
0752             debug() << "Restoring buffering state after state change to Paused";
0753             changeState(BufferingState);
0754             m_stateAfterBuffering = PausedState;
0755             break;
0756         default:
0757             debug() << "Buffering aborted!";
0758             m_buffering = false;
0759             break;
0760         }
0761     }
0762 
0763     return;
0764 }
0765 
0766 void MediaObject::onHasVideoChanged(bool hasVideo)
0767 {
0768     DEBUG_BLOCK;
0769     if (m_hasVideo != hasVideo) {
0770         m_hasVideo = hasVideo;
0771         emit hasVideoChanged(m_hasVideo);
0772     } else {
0773         // We can simply return if we are have the appropriate caching already.
0774         // Otherwise we'd do pointless rescans of mediacontroller stuff.
0775         // MC and MO are force-reset on media changes anyway.
0776         return;
0777     }
0778 
0779     refreshDescriptors();
0780 }
0781 
0782 void MediaObject::setBufferStatus(int percent)
0783 {
0784     // VLC does not have a buffering state (surprise!) but instead only sends the
0785     // event (surprise!). Hence we need to simulate the state change.
0786     // Problem with BufferingState is that it is actually concurrent to Playing or Paused
0787     // meaning that while you are buffering you can also pause, thus triggering
0788     // a state change to paused. To handle this we let updateState change the
0789     // state accordingly (as we need to allow the UI to update itself, and
0790     // immediately after that we change back to buffering again.
0791     // This loop can only be interrupted by a state change to !Playing & !Paused
0792     // or by reaching a 100 % buffer caching (i.e. full cache).
0793 
0794     m_buffering = true;
0795     if (m_state != BufferingState) {
0796         m_stateAfterBuffering = m_state;
0797         changeState(BufferingState);
0798     }
0799 
0800     emit bufferStatus(percent);
0801 
0802     // Transit to actual state only after emission so the signal is still
0803     // delivered while in BufferingState.
0804     if (percent >= 100) { // http://trac.videolan.org/vlc/ticket/5277
0805         m_buffering = false;
0806         changeState(m_stateAfterBuffering);
0807     }
0808 }
0809 
0810 void MediaObject::refreshDescriptors()
0811 {
0812     if (m_player->titleCount() > 0)
0813         refreshTitles();
0814 
0815     if (hasVideo()) {
0816         refreshAudioChannels();
0817         refreshSubtitles();
0818 
0819         if (m_player->videoChapterCount() > 0)
0820             refreshChapters(m_player->title());
0821     }
0822 }
0823 
0824 qint64 MediaObject::totalTime() const
0825 {
0826     return m_totalTime;
0827 }
0828 
0829 void MediaObject::addSink(SinkNode *node)
0830 {
0831     Q_ASSERT(!m_sinks.contains(node));
0832     m_sinks.append(node);
0833 }
0834 
0835 void MediaObject::removeSink(SinkNode *node)
0836 {
0837     Q_ASSERT(node);
0838     m_sinks.removeAll(node);
0839 }
0840 
0841 } // namespace VLC
0842 } // namespace Phonon