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