diff --git a/src/backend.cpp b/src/backend.cpp index 700ce12..80f0f92 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -1,365 +1,365 @@ /* Copyright (C) 2007-2008 Tanguy Krotoff Copyright (C) 2008 Lukas Durfina Copyright (C) 2009 Fathi Boudra Copyright (C) 2009-2011 vlc-phonon AUTHORS Copyright (C) 2011-2013 Harald Sitter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "backend.h" #include #include -#include +#include #include #include #include #include #include #include #include "audio/audiooutput.h" #include "audio/audiodataoutput.h" #include "audio/volumefadereffect.h" #include "config.h" #include "devicemanager.h" #include "effect.h" #include "effectmanager.h" #include "mediaobject.h" #include "sinknode.h" #include "utils/debug.h" #include "utils/libvlc.h" #include "utils/mime.h" #ifdef PHONON_EXPERIMENTAL #include "video/videodataoutput.h" #endif #include "video/videowidget.h" namespace Phonon { namespace VLC { Backend *Backend::self; Backend::Backend(QObject *parent, const QVariantList &) : QObject(parent) , m_deviceManager(0) , m_effectManager(0) { self = this; // Backend information properties setProperty("identifier", QLatin1String("phonon_vlc")); setProperty("backendName", QLatin1String("VLC")); setProperty("backendComment", QLatin1String("VLC backend for Phonon")); setProperty("backendVersion", QLatin1String(PHONON_VLC_VERSION)); setProperty("backendIcon", QLatin1String("vlc")); setProperty("backendWebsite", QLatin1String("https://projects.kde.org/projects/kdesupport/phonon/phonon-vlc")); // Check if we should enable debug output int debugLevel = qgetenv("PHONON_BACKEND_DEBUG").toInt(); if (debugLevel > 3) // 3 is maximum debugLevel = 3; Debug::setMinimumDebugLevel((Debug::DebugLevel)((int) Debug::DEBUG_NONE - 1 - debugLevel)); debug() << "Constructing Phonon-VLC Version" << PHONON_VLC_VERSION; // Actual libVLC initialisation if (LibVLC::init()) { debug() << "Using VLC version" << libvlc_get_version(); if (!qApp->applicationName().isEmpty()) { QString userAgent = QString("%0/%1 (Phonon/%2; Phonon-VLC/%3)").arg( qApp->applicationName(), qApp->applicationVersion(), PHONON_VERSION_STR, PHONON_VLC_VERSION); libvlc_set_user_agent(pvlc_libvlc, qApp->applicationName().toUtf8().constData(), userAgent.toUtf8().constData()); } else { qWarning("WARNING: Setting the user agent for streaming and" " PulseAudio requires you to set QCoreApplication::applicationName()"); } PulseSupport::getInstance()->enable(true); const bool pulseActive = PulseSupport::getInstance()->isActive(); PulseSupport::getInstance()->enable(false); if (!qApp->applicationName().isEmpty()) { const QString id = QString("org.kde.phonon.%1").arg(qApp->applicationName()); const QString version = qApp->applicationVersion(); QString icon; if (!qApp->windowIcon().isNull()){ // Try to get the fromTheme() name of the QIcon. icon = qApp->windowIcon().name(); } if (icon.isEmpty()) { // If we failed to get a proper icon name, use the appname instead. icon = qApp->applicationName().toLower(); } libvlc_set_app_id(pvlc_libvlc, id.toUtf8().constData(), version.toUtf8().constData(), icon.toUtf8().constData()); } else if (pulseActive) { qWarning("WARNING: Setting PulseAudio context information requires you" " to set QCoreApplication::applicationName()," " QCoreApplication::applicationVersion() and" " QGuiApplication::windowIcon()."); } } else { #ifdef __GNUC__ #warning TODO - this error message is as useful as a knife at a gun fight #endif QMessageBox msg; msg.setIcon(QMessageBox::Critical); msg.setWindowTitle(tr("LibVLC Failed to Initialize")); msg.setText(tr("Phonon's VLC backend failed to start." "\n\n" "This usually means a problem with your VLC installation," " please report a bug with your distributor.")); msg.setDetailedText(LibVLC::errorMessage()); msg.exec(); fatal() << "Phonon::VLC::vlcInit: Failed to initialize VLC"; } #if (LIBVLC_VERSION_INT < LIBVLC_VERSION(2, 2, 2, 0)) // VLC 2.2 changed the stream creation order around internally which breaks // the Pulseaudio hijacking. Since VLC upstream doesn't feel like giving us // any more property control we now consider this feature unsupported. As // such whatever properties VLC sets will be what pulse knows about us. // Initialise PulseAudio support PulseSupport *pulse = PulseSupport::getInstance(); pulse->enable(true); connect(pulse, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)), SIGNAL(objectDescriptionChanged(ObjectDescriptionType))); #endif m_deviceManager = new DeviceManager(this); m_effectManager = new EffectManager(this); } Backend::~Backend() { if (LibVLC::self) delete LibVLC::self; if (GlobalAudioChannels::self) delete GlobalAudioChannels::self; if (GlobalSubtitles::self) delete GlobalSubtitles::self; PulseSupport::shutdown(); } QObject *Backend::createObject(BackendInterface::Class c, QObject *parent, const QList &args) { if (!LibVLC::self || !pvlc_libvlc) return 0; switch (c) { case MediaObjectClass: return new MediaObject(parent); case AudioOutputClass: return new AudioOutput(parent); #if (LIBVLC_VERSION_INT < LIBVLC_VERSION(2, 0, 0, 0)) // Broken >= 2.0 // https://trac.videolan.org/vlc/ticket/6992 case AudioDataOutputClass: return new AudioDataOutput(parent); #endif #ifdef PHONON_EXPERIMENTAL case VideoDataOutputClass: return new VideoDataOutput(parent); #endif case VideoGraphicsObjectClass: return nullptr; // No longer supported case EffectClass: return effectManager()->createEffect(args[0].toInt(), parent); case VideoWidgetClass: return new VideoWidget(qobject_cast(parent)); // case VolumeFaderEffectClass: #ifdef __GNUC__ #warning VFE crashes and has volume bugs ... deactivated // return new VolumeFaderEffect(parent); #endif } warning() << "Backend class" << c << "is not supported by Phonon VLC :("; return 0; } QStringList Backend::availableMimeTypes() const { if (m_supportedMimeTypes.isEmpty()) const_cast(this)->m_supportedMimeTypes = mimeTypeList(); return m_supportedMimeTypes; } QList Backend::objectDescriptionIndexes(ObjectDescriptionType type) const { QList list; switch (type) { case Phonon::AudioChannelType: { list << GlobalAudioChannels::instance()->globalIndexes(); } break; case Phonon::AudioOutputDeviceType: case Phonon::AudioCaptureDeviceType: case Phonon::VideoCaptureDeviceType: { return deviceManager()->deviceIds(type); } break; case Phonon::EffectType: { QList effectList = effectManager()->effects(); for (int eff = 0; eff < effectList.size(); ++eff) { list.append(eff); } } break; case Phonon::SubtitleType: { list << GlobalSubtitles::instance()->globalIndexes(); } break; } return list; } QHash Backend::objectDescriptionProperties(ObjectDescriptionType type, int index) const { QHash ret; switch (type) { case Phonon::AudioChannelType: { const AudioChannelDescription description = GlobalAudioChannels::instance()->fromIndex(index); ret.insert("name", description.name()); ret.insert("description", description.description()); } break; case Phonon::AudioOutputDeviceType: case Phonon::AudioCaptureDeviceType: case Phonon::VideoCaptureDeviceType: { // Index should be unique, even for different categories return deviceManager()->deviceProperties(index); } break; case Phonon::EffectType: { const QList effectList = effectManager()->effects(); if (index >= 0 && index <= effectList.size()) { const EffectInfo &effect = effectList.at(index); ret.insert("name", effect.name()); ret.insert("description", effect.description()); ret.insert("author", effect.author()); } else { Q_ASSERT(1); // Since we use list position as ID, this should not happen } } break; case Phonon::SubtitleType: { const SubtitleDescription description = GlobalSubtitles::instance()->fromIndex(index); ret.insert("name", description.name()); ret.insert("description", description.description()); ret.insert("type", description.property("type")); } break; } return ret; } bool Backend::startConnectionChange(QSet objects) { //FIXME foreach(QObject * object, objects) { debug() << "Object:" << object->metaObject()->className(); } // There is nothing we can do but hope the connection changes will not take too long // so that buffers would underrun // But we should be pretty safe the way xine works by not doing anything here. return true; } bool Backend::connectNodes(QObject *source, QObject *sink) { debug() << "Backend connected" << source->metaObject()->className() << "to" << sink->metaObject()->className(); SinkNode *sinkNode = dynamic_cast(sink); if (sinkNode) { MediaObject *mediaObject = qobject_cast(source); if (mediaObject) { // Connect the SinkNode to a MediaObject sinkNode->connectToMediaObject(mediaObject); return true; } VolumeFaderEffect *effect = qobject_cast(source); if (effect) { sinkNode->connectToMediaObject(effect->mediaObject()); return true; } } warning() << "Linking" << source->metaObject()->className() << "to" << sink->metaObject()->className() << "failed"; return false; } bool Backend::disconnectNodes(QObject *source, QObject *sink) { SinkNode *sinkNode = dynamic_cast(sink); if (sinkNode) { MediaObject *const mediaObject = qobject_cast(source); if (mediaObject) { // Disconnect the SinkNode from a MediaObject sinkNode->disconnectFromMediaObject(mediaObject); return true; } VolumeFaderEffect *const effect = qobject_cast(source); if (effect) { sinkNode->disconnectFromMediaObject(effect->mediaObject()); return true; } } return false; } bool Backend::endConnectionChange(QSet objects) { foreach(QObject *object, objects) { debug() << "Object:" << object->metaObject()->className(); } return true; } DeviceManager *Backend::deviceManager() const { return m_deviceManager; } EffectManager *Backend::effectManager() const { return m_effectManager; } } // namespace VLC } // namespace Phonon diff --git a/src/mediaobject.cpp b/src/mediaobject.cpp index 8069e7b..1f4af72 100644 --- a/src/mediaobject.cpp +++ b/src/mediaobject.cpp @@ -1,826 +1,826 @@ /* Copyright (C) 2007-2008 Tanguy Krotoff Copyright (C) 2008 Lukas Durfina Copyright (C) 2009 Fathi Boudra Copyright (C) 2010 Ben Cooksley Copyright (C) 2009-2011 vlc-phonon AUTHORS Copyright (C) 2010-2015 Harald Sitter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "mediaobject.h" #include #include #include #include #include #include "utils/debug.h" #include "utils/libvlc.h" #include "media.h" #include "sinknode.h" #include "streamreader.h" //Time in milliseconds before sending aboutToFinish() signal //2 seconds static const int ABOUT_TO_FINISH_TIME = 2000; namespace Phonon { namespace VLC { MediaObject::MediaObject(QObject *parent) : QObject(parent) , m_nextSource(MediaSource(QUrl())) , m_streamReader(0) , m_state(Phonon::StoppedState) , m_tickInterval(0) , m_transitionTime(0) , m_media(0) { qRegisterMetaType >("QMultiMap"); m_player = new MediaPlayer(this); if (!m_player->libvlc_media_player()) error() << "libVLC:" << LibVLC::errorMessage(); // Player signals. connect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool))); connect(m_player, SIGNAL(timeChanged(qint64)), this, SLOT(timeChanged(qint64))); connect(m_player, SIGNAL(stateChanged(MediaPlayer::State)), this, SLOT(updateState(MediaPlayer::State))); connect(m_player, SIGNAL(hasVideoChanged(bool)), this, SLOT(onHasVideoChanged(bool))); connect(m_player, SIGNAL(bufferChanged(int)), this, SLOT(setBufferStatus(int))); connect(m_player, SIGNAL(timeChanged(qint64)), this, SLOT(timeChanged(qint64))); // Internal Signals. connect(this, SIGNAL(moveToNext()), SLOT(moveToNextSource())); connect(m_refreshTimer, SIGNAL(timeout()), this, SLOT(refreshDescriptors())); resetMembers(); } MediaObject::~MediaObject() { unloadMedia(); } void MediaObject::resetMembers() { // default to -1, so that streams won't break and to comply with the docs (-1 if unknown) m_totalTime = -1; m_hasVideo = false; m_seekpoint = 0; m_prefinishEmitted = false; m_aboutToFinishEmitted = false; m_lastTick = 0; m_timesVideoChecked = 0; m_buffering = false; m_stateAfterBuffering = ErrorState; resetMediaController(); } void MediaObject::play() { DEBUG_BLOCK; switch (m_state) { case PlayingState: // Do not do anything if we are already playing (as per documentation). return; case PausedState: m_player->resume(); break; default: setupMedia(); if (m_player->play()) error() << "libVLC:" << LibVLC::errorMessage(); break; } } void MediaObject::pause() { DEBUG_BLOCK; switch (m_state) { case BufferingState: case PlayingState: m_player->pause(); break; case PausedState: return; default: debug() << "doing paused play"; setupMedia(); m_player->pausedPlay(); break; } } void MediaObject::stop() { DEBUG_BLOCK; if (m_streamReader) m_streamReader->unlock(); m_nextSource = MediaSource(QUrl()); m_player->stop(); } void MediaObject::seek(qint64 milliseconds) { DEBUG_BLOCK; switch (m_state) { case PlayingState: case PausedState: case BufferingState: break; default: // Seeking while not being in a playingish state is cached for later. m_seekpoint = milliseconds; return; } debug() << "seeking" << milliseconds << "msec"; m_player->setTime(milliseconds); const qint64 time = currentTime(); const qint64 total = totalTime(); // Reset last tick marker so we emit time even after seeking if (time < m_lastTick) m_lastTick = time; if (time < total - m_prefinishMark) m_prefinishEmitted = false; if (time < total - ABOUT_TO_FINISH_TIME) m_aboutToFinishEmitted = false; } void MediaObject::timeChanged(qint64 time) { const qint64 totalTime = m_totalTime; switch (m_state) { case PlayingState: case BufferingState: case PausedState: emitTick(time); default: break; } if (m_state == PlayingState || m_state == BufferingState) { // Buffering is concurrent if (time >= totalTime - m_prefinishMark) { if (!m_prefinishEmitted) { m_prefinishEmitted = true; emit prefinishMarkReached(totalTime - time); } } // Note that when the totalTime is <= 0 we cannot calculate any sane delta. if (totalTime > 0 && time >= totalTime - ABOUT_TO_FINISH_TIME) emitAboutToFinish(); } } void MediaObject::emitTick(qint64 time) { if (m_tickInterval == 0) // Make sure we do not ever emit ticks when deactivated.\] return; if (time + m_tickInterval >= m_lastTick) { m_lastTick = time; emit tick(time); } } void MediaObject::loadMedia(const QByteArray &mrl) { DEBUG_BLOCK; // Initial state is loading, from which we quickly progress to stopped because // libvlc does not provide feedback on loading and the media does not get loaded // until we play it. // FIXME: libvlc should really allow for this as it can cause unexpected delay // even though the GUI might indicate that playback should start right away. changeState(Phonon::LoadingState); m_mrl = mrl; debug() << "loading encoded:" << m_mrl; // We do not have a loading state generally speaking, usually the backend // is exepected to go to loading state and then at some point reach stopped, // at which point playback can be started. // See state enum documentation for more information. changeState(Phonon::StoppedState); } void MediaObject::loadMedia(const QString &mrl) { loadMedia(mrl.toUtf8()); } qint32 MediaObject::tickInterval() const { return m_tickInterval; } /** * Supports runtime changes. * If the user goes to tick(0) we stop the timer, otherwise we fire it up. */ void MediaObject::setTickInterval(qint32 interval) { m_tickInterval = interval; } qint64 MediaObject::currentTime() const { qint64 time = -1; switch (state()) { case Phonon::PausedState: case Phonon::BufferingState: case Phonon::PlayingState: time = m_player->time(); break; case Phonon::StoppedState: case Phonon::LoadingState: time = 0; break; case Phonon::ErrorState: time = -1; break; } return time; } Phonon::State MediaObject::state() const { return m_state; } Phonon::ErrorType MediaObject::errorType() const { return Phonon::NormalError; } MediaSource MediaObject::source() const { return m_mediaSource; } void MediaObject::setSource(const MediaSource &source) { DEBUG_BLOCK; // Reset previous streamereaders if (m_streamReader) { m_streamReader->unlock(); delete m_streamReader; m_streamReader = 0; // For streamreaders we exchanage the player's seekability with the // reader's so here we change it back. // Note: the reader auto-disconnects due to destruction. connect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool))); } // Reset previous isScreen flag m_isScreen = false; m_mediaSource = source; QByteArray url; switch (source.type()) { case MediaSource::Invalid: error() << Q_FUNC_INFO << "MediaSource Type is Invalid:" << source.type(); break; case MediaSource::Empty: error() << Q_FUNC_INFO << "MediaSource is empty."; break; case MediaSource::LocalFile: case MediaSource::Url: debug() << "MediaSource::Url:" << source.url(); if (source.url().scheme().isEmpty()) { url = "file://"; // QUrl considers url.scheme.isEmpty() == url.isRelative(), // so to be sure the url is not actually absolute we just // check the first character if (!source.url().toString().startsWith('/')) url.append(QFile::encodeName(QDir::currentPath()) + '/'); } url += source.url().toEncoded(); loadMedia(url); break; case MediaSource::Disc: switch (source.discType()) { case Phonon::NoDisc: error() << Q_FUNC_INFO << "the MediaSource::Disc doesn't specify which one (Phonon::NoDisc)"; return; case Phonon::Cd: - loadMedia(QLatin1String("cdda://") % m_mediaSource.deviceName()); + loadMedia(QLatin1Literal("cdda://") % m_mediaSource.deviceName()); break; case Phonon::Dvd: - loadMedia(QLatin1String("dvd://") % m_mediaSource.deviceName()); + loadMedia(QLatin1Literal("dvd://") % m_mediaSource.deviceName()); break; case Phonon::Vcd: - loadMedia(QLatin1String("vcd://") % m_mediaSource.deviceName()); + loadMedia(QLatin1Literal("vcd://") % m_mediaSource.deviceName()); break; case Phonon::BluRay: - loadMedia(QLatin1String("bluray://") % m_mediaSource.deviceName()); + loadMedia(QLatin1Literal("bluray://") % m_mediaSource.deviceName()); break; } break; case MediaSource::CaptureDevice: { QByteArray driverName; QString deviceName; if (source.deviceAccessList().isEmpty()) { error() << Q_FUNC_INFO << "No device access list for this capture device"; break; } // TODO try every device in the access list until it works, not just the first one driverName = source.deviceAccessList().first().first; deviceName = source.deviceAccessList().first().second; if (driverName == QByteArray("v4l2")) { - loadMedia(QLatin1String("v4l2://") % deviceName); + loadMedia(QLatin1Literal("v4l2://") % deviceName); } else if (driverName == QByteArray("alsa")) { /* * Replace "default" and "plughw" and "x-phonon" with "hw" for capture device names, because * VLC does not want to open them when using default instead of hw. * plughw also does not work. * * TODO investigate what happens */ if (deviceName.startsWith(QLatin1String("default"))) { deviceName.replace(0, 7, "hw"); } if (deviceName.startsWith(QLatin1String("plughw"))) { deviceName.replace(0, 6, "hw"); } if (deviceName.startsWith(QLatin1String("x-phonon"))) { deviceName.replace(0, 8, "hw"); } - loadMedia(QLatin1String("alsa://") % deviceName); + loadMedia(QLatin1Literal("alsa://") % deviceName); } else if (driverName == "screen") { - loadMedia(QLatin1String("screen://") % deviceName); + loadMedia(QLatin1Literal("screen://") % deviceName); // Set the isScreen flag needed to add extra options in playInternal m_isScreen = true; } else { error() << Q_FUNC_INFO << "Unsupported MediaSource::CaptureDevice:" << driverName; break; } break; } case MediaSource::Stream: m_streamReader = new StreamReader(this); // LibVLC refuses to emit seekability as it does a try-and-seek approach // to work around this we exchange the player's seekability signal // for the readers // https://bugs.kde.org/show_bug.cgi?id=293012 connect(m_streamReader, SIGNAL(streamSeekableChanged(bool)), this, SIGNAL(seekableChanged(bool))); disconnect(m_player, SIGNAL(seekableChanged(bool)), this, SIGNAL(seekableChanged(bool))); // Only connect now to avoid seekability detection before we are connected. m_streamReader->connectToSource(source); loadMedia(QByteArray("imem://")); break; } debug() << "Sending currentSourceChanged"; emit currentSourceChanged(m_mediaSource); } void MediaObject::setNextSource(const MediaSource &source) { DEBUG_BLOCK; debug() << source.url(); m_nextSource = source; // This function is not ever called by the consumer but only libphonon. // Furthermore libphonon only calls this function in its aboutToFinish slot, // iff sources are already in the queue. In case our aboutToFinish was too // late we may already be stopped when the slot gets activated. // Therefore we need to make sure that we move to the next source iff // this function is called when we are in stoppedstate. if (m_state == StoppedState) moveToNext(); } qint32 MediaObject::prefinishMark() const { return m_prefinishMark; } void MediaObject::setPrefinishMark(qint32 msecToEnd) { m_prefinishMark = msecToEnd; if (currentTime() < totalTime() - m_prefinishMark) { // Not about to finish m_prefinishEmitted = false; } } qint32 MediaObject::transitionTime() const { return m_transitionTime; } void MediaObject::setTransitionTime(qint32 time) { m_transitionTime = time; } void MediaObject::emitAboutToFinish() { if (!m_aboutToFinishEmitted) { // Track is about to finish m_aboutToFinishEmitted = true; emit aboutToFinish(); } } // State changes are force queued by libphonon. void MediaObject::changeState(Phonon::State newState) { DEBUG_BLOCK; // State not changed if (newState == m_state) return; debug() << m_state << "-->" << newState; #ifdef __GNUC__ #warning do we actually need m_seekpoint? if a consumer seeks before playing state that is their problem?! #endif // Workaround that seeking needs to work before the file is being played... // We store seeks and apply them when going to seek (or discard them on reset). if (newState == PlayingState) { if (m_seekpoint != 0) { seek(m_seekpoint); m_seekpoint = 0; } } // State changed Phonon::State previousState = m_state; m_state = newState; emit stateChanged(m_state, previousState); } void MediaObject::moveToNextSource() { DEBUG_BLOCK; setSource(m_nextSource); // The consumer may set an invalid source as final source to force a // queued stop, regardless of how fast the consumer is at actually calling // stop. Such a source must not cause an actual move (moving ~= state // changes towards playing) but instead we only set the source to reflect // that we got the setNextSource call. if (hasNextTrack()) play(); m_nextSource = MediaSource(QUrl()); } inline bool MediaObject::hasNextTrack() { return m_nextSource.type() != MediaSource::Invalid && m_nextSource.type() != MediaSource::Empty; } inline void MediaObject::unloadMedia() { if (m_media) { m_media->disconnect(this); m_media->deleteLater(); m_media = 0; } } void MediaObject::setupMedia() { DEBUG_BLOCK; unloadMedia(); resetMembers(); // Create a media with the given MRL m_media = new Media(m_mrl, this); if (!m_media) error() << "libVLC:" << LibVLC::errorMessage(); if (m_isScreen) { m_media->addOption(QLatin1String("screen-fps=24.0")); m_media->addOption(QLatin1String("screen-caching=300")); } if (source().discType() == Cd && m_currentTitle > 0) m_media->setCdTrack(m_currentTitle); if (m_streamReader) // StreamReader is no sink but a source, for this we have no concept right now // also we do not need one since the reader is the only source we have. // Consequently we need to manually tell the StreamReader to attach to the Media. m_streamReader->addToMedia(m_media); if (!m_subtitleAutodetect) m_media->addOption(QLatin1String(":no-sub-autodetect-file")); if (m_subtitleEncoding != QLatin1String("UTF-8")) // utf8 is phonon default, so let vlc handle it m_media->addOption(QLatin1String(":subsdec-encoding="), m_subtitleEncoding); if (!m_subtitleFontChanged) // Update font settings m_subtitleFont = QFont(); #ifdef __GNUC__ #warning freetype module is not working as expected - font api not working #endif // BUG: VLC's freetype module doesn't pick up per-media options // vlc -vvvv --freetype-font="Comic Sans MS" multiple_sub_sample.mkv :freetype-font=Arial // https://trac.videolan.org/vlc/ticket/9797 m_media->addOption(QLatin1String(":freetype-font="), m_subtitleFont.family()); m_media->addOption(QLatin1String(":freetype-fontsize="), m_subtitleFont.pointSize()); if (m_subtitleFont.bold()) m_media->addOption(QLatin1String(":freetype-bold")); else m_media->addOption(QLatin1String(":no-freetype-bold")); foreach (SinkNode *sink, m_sinks) { sink->addToMedia(m_media); } // Connect to Media signals. Disconnection is done at unloading. connect(m_media, SIGNAL(durationChanged(qint64)), this, SLOT(updateDuration(qint64))); connect(m_media, SIGNAL(metaDataChanged()), this, SLOT(updateMetaData())); // Update available audio channels/subtitles/angles/chapters/etc... // i.e everything from MediaController // There is no audio channel/subtitle/angle/chapter events inside libvlc // so let's send our own events... // This will reset the GUI resetMediaController(); // Play m_player->setMedia(m_media); } QString MediaObject::errorString() const { return libvlc_errmsg(); } bool MediaObject::hasVideo() const { // Cached: sometimes 4.0.0-dev sends the vout event but then // has_vout is still false. Guard against this by simply always reporting // the last hasVideoChanged value. If that is off we can still drop into // libvlc in case it changed meanwhile. return m_hasVideo || m_player->hasVideoOutput(); } bool MediaObject::isSeekable() const { if (m_streamReader) return m_streamReader->streamSeekable(); return m_player->isSeekable(); } void MediaObject::updateDuration(qint64 newDuration) { // This here cache is needed because we need to provide -1 as totalTime() // for as long as we do not get a proper update through this slot. // VLC reports -1 with no media but 0 if it does not know the duration, so // apps that assume 0 = unknown get screwed if they query too early. // http://bugs.tomahawk-player.org/browse/TWK-1029 m_totalTime = newDuration; emit totalTimeChanged(m_totalTime); } void MediaObject::updateMetaData() { QMultiMap metaDataMap; const QString artist = m_media->meta(libvlc_meta_Artist); const QString title = m_media->meta(libvlc_meta_Title); const QString nowPlaying = m_media->meta(libvlc_meta_NowPlaying); // Streams sometimes have the artist and title munged in nowplaying. // With ALBUM = Title and TITLE = NowPlaying it will still show up nicely in Amarok. if (artist.isEmpty() && !nowPlaying.isEmpty()) { metaDataMap.insert(QLatin1String("ALBUM"), title); metaDataMap.insert(QLatin1String("TITLE"), nowPlaying); } else { metaDataMap.insert(QLatin1String("ALBUM"), m_media->meta(libvlc_meta_Album)); metaDataMap.insert(QLatin1String("TITLE"), title); } metaDataMap.insert(QLatin1String("ARTIST"), artist); metaDataMap.insert(QLatin1String("DATE"), m_media->meta(libvlc_meta_Date)); metaDataMap.insert(QLatin1String("GENRE"), m_media->meta(libvlc_meta_Genre)); metaDataMap.insert(QLatin1String("TRACKNUMBER"), m_media->meta(libvlc_meta_TrackNumber)); metaDataMap.insert(QLatin1String("DESCRIPTION"), m_media->meta(libvlc_meta_Description)); metaDataMap.insert(QLatin1String("COPYRIGHT"), m_media->meta(libvlc_meta_Copyright)); metaDataMap.insert(QLatin1String("URL"), m_media->meta(libvlc_meta_URL)); metaDataMap.insert(QLatin1String("ENCODEDBY"), m_media->meta(libvlc_meta_EncodedBy)); if (metaDataMap == m_vlcMetaData) { // No need to issue any change, the data is the same return; } m_vlcMetaData = metaDataMap; emit metaDataChanged(metaDataMap); } void MediaObject::updateState(MediaPlayer::State state) { DEBUG_BLOCK; debug() << state; debug() << "attempted autoplay?" << m_attemptingAutoplay; if (m_attemptingAutoplay) { switch (state) { case MediaPlayer::PlayingState: case MediaPlayer::PausedState: m_attemptingAutoplay = false; break; case MediaPlayer::ErrorState: debug() << "autoplay failed, must be end of media."; // The error should not be reflected to the consumer. So we swap it // for finished() which is actually what is happening here. // Or so we think ;) state = MediaPlayer::EndedState; --m_currentTitle; break; default: debug() << "not handling as part of autplay:" << state; break; } } switch (state) { case MediaPlayer::NoState: changeState(LoadingState); break; case MediaPlayer::OpeningState: changeState(LoadingState); break; case MediaPlayer::BufferingState: changeState(BufferingState); break; case MediaPlayer::PlayingState: changeState(PlayingState); break; case MediaPlayer::PausedState: changeState(PausedState); break; case MediaPlayer::StoppedState: changeState(StoppedState); break; case MediaPlayer::EndedState: if (hasNextTrack()) { moveToNextSource(); } else if (source().discType() == Cd && m_autoPlayTitles && !m_attemptingAutoplay) { debug() << "trying to simulate autoplay"; m_attemptingAutoplay = true; m_player->setCdTrack(++m_currentTitle); } else { m_attemptingAutoplay = false; emitAboutToFinish(); emit finished(); changeState(StoppedState); } break; case MediaPlayer::ErrorState: debug() << errorString(); emitAboutToFinish(); emit finished(); changeState(ErrorState); break; } if (m_buffering) { switch (state) { case MediaPlayer::BufferingState: break; case MediaPlayer::PlayingState: debug() << "Restoring buffering state after state change to Playing"; changeState(BufferingState); m_stateAfterBuffering = PlayingState; break; case MediaPlayer::PausedState: debug() << "Restoring buffering state after state change to Paused"; changeState(BufferingState); m_stateAfterBuffering = PausedState; break; default: debug() << "Buffering aborted!"; m_buffering = false; break; } } return; } void MediaObject::onHasVideoChanged(bool hasVideo) { DEBUG_BLOCK; if (m_hasVideo != hasVideo) { m_hasVideo = hasVideo; emit hasVideoChanged(m_hasVideo); } else { // We can simply return if we are have the appropriate caching already. // Otherwise we'd do pointless rescans of mediacontroller stuff. // MC and MO are force-reset on media changes anyway. return; } refreshDescriptors(); } void MediaObject::setBufferStatus(int percent) { // VLC does not have a buffering state (surprise!) but instead only sends the // event (surprise!). Hence we need to simulate the state change. // Problem with BufferingState is that it is actually concurrent to Playing or Paused // meaning that while you are buffering you can also pause, thus triggering // a state change to paused. To handle this we let updateState change the // state accordingly (as we need to allow the UI to update itself, and // immediately after that we change back to buffering again. // This loop can only be interrupted by a state change to !Playing & !Paused // or by reaching a 100 % buffer caching (i.e. full cache). m_buffering = true; if (m_state != BufferingState) { m_stateAfterBuffering = m_state; changeState(BufferingState); } emit bufferStatus(percent); // Transit to actual state only after emission so the signal is still // delivered while in BufferingState. if (percent >= 100) { // http://trac.videolan.org/vlc/ticket/5277 m_buffering = false; changeState(m_stateAfterBuffering); } } void MediaObject::refreshDescriptors() { if (m_player->titleCount() > 0) refreshTitles(); if (hasVideo()) { refreshAudioChannels(); refreshSubtitles(); if (m_player->videoChapterCount() > 0) refreshChapters(m_player->title()); } } qint64 MediaObject::totalTime() const { return m_totalTime; } void MediaObject::addSink(SinkNode *node) { Q_ASSERT(!m_sinks.contains(node)); m_sinks.append(node); } void MediaObject::removeSink(SinkNode *node) { Q_ASSERT(node); m_sinks.removeAll(node); } } // namespace VLC } // namespace Phonon diff --git a/src/mediaplayer.cpp b/src/mediaplayer.cpp index c60ca22..e489280 100644 --- a/src/mediaplayer.cpp +++ b/src/mediaplayer.cpp @@ -1,431 +1,431 @@ /* Copyright (C) 2011-2018 Harald Sitter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "mediaplayer.h" #include #include #include #include #include #include #include #include "utils/libvlc.h" #include "media.h" // Callbacks come from a VLC thread. In some cases Qt fails to detect this and // tries to invoke directly (i.e. from same thread). This can lead to thread // pollution throughout Phonon, which is very much not desired. #define P_EMIT_HAS_VIDEO(hasVideo) \ QMetaObject::invokeMethod(\ that, "hasVideoChanged", \ Qt::QueuedConnection, \ Q_ARG(bool, hasVideo)) #define P_EMIT_STATE(__state) \ QMetaObject::invokeMethod(\ that, "stateChanged", \ Qt::QueuedConnection, \ Q_ARG(MediaPlayer::State, __state)) namespace Phonon { namespace VLC { MediaPlayer::MediaPlayer(QObject *parent) : QObject(parent) , m_media(0) , m_player(libvlc_media_player_new(pvlc_libvlc)) , m_doingPausedPlay(false) , m_volume(75) , m_fadeAmount(1.0f) { Q_ASSERT(m_player); qRegisterMetaType("MediaPlayer::State"); libvlc_event_manager_t *manager = libvlc_media_player_event_manager(m_player); libvlc_event_type_t events[] = { libvlc_MediaPlayerMediaChanged, libvlc_MediaPlayerNothingSpecial, libvlc_MediaPlayerOpening, libvlc_MediaPlayerBuffering, libvlc_MediaPlayerPlaying, libvlc_MediaPlayerPaused, libvlc_MediaPlayerStopped, libvlc_MediaPlayerForward, libvlc_MediaPlayerBackward, libvlc_MediaPlayerEndReached, libvlc_MediaPlayerEncounteredError, libvlc_MediaPlayerTimeChanged, libvlc_MediaPlayerPositionChanged, libvlc_MediaPlayerSeekableChanged, libvlc_MediaPlayerPausableChanged, libvlc_MediaPlayerTitleChanged, libvlc_MediaPlayerSnapshotTaken, libvlc_MediaPlayerLengthChanged, libvlc_MediaPlayerVout #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 2, 0)) , libvlc_MediaPlayerCorked, libvlc_MediaPlayerUncorked, libvlc_MediaPlayerMuted, libvlc_MediaPlayerUnmuted, libvlc_MediaPlayerAudioVolume #endif }; const int eventCount = sizeof(events) / sizeof(*events); for (int i = 0; i < eventCount; ++i) { libvlc_event_attach(manager, events[i], event_cb, this); } // Deactivate video title overlay (i.e. name of the video displaying // at start. Since 2.1 that is handled via the API which in general is more // reliable than setting it via libvlc_new (or so I have been told....) libvlc_media_player_set_video_title_display(m_player, libvlc_position_disable, 0); } MediaPlayer::~MediaPlayer() { libvlc_media_player_release(m_player); } void MediaPlayer::setMedia(Media *media) { m_media = media; libvlc_media_player_set_media(m_player, *m_media); } bool MediaPlayer::play() { m_doingPausedPlay = false; return libvlc_media_player_play(m_player) == 0; } void MediaPlayer::pause() { m_doingPausedPlay = false; libvlc_media_player_set_pause(m_player, 1); } void MediaPlayer::pausedPlay() { m_doingPausedPlay = true; libvlc_media_player_play(m_player); } void MediaPlayer::resume() { m_doingPausedPlay = false; libvlc_media_player_set_pause(m_player, 0); } void MediaPlayer::togglePause() { libvlc_media_player_pause(m_player); } void MediaPlayer::stop() { m_doingPausedPlay = false; #ifdef __GNUC__ #warning changed to stop_async does this have impliciations #endif #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0)) libvlc_media_player_stop_async(m_player); #else libvlc_media_player_stop(m_player); #endif } qint64 MediaPlayer::length() const { return libvlc_media_player_get_length(m_player); } qint64 MediaPlayer::time() const { return libvlc_media_player_get_time(m_player); } void MediaPlayer::setTime(qint64 newTime) { #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0)) libvlc_media_player_set_time(m_player, newTime, false /* not fast, but precise */); #else libvlc_media_player_set_time(m_player, newTime); #endif } bool MediaPlayer::isSeekable() const { return libvlc_media_player_is_seekable(m_player); } bool MediaPlayer::hasVideoOutput() const { return libvlc_media_player_has_vout(m_player) > 0; } bool MediaPlayer::setSubtitle(int subtitle) { return libvlc_video_set_spu(m_player, subtitle) == 0; } bool MediaPlayer::setSubtitle(const QString &file) { #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(3, 0, 0, 0)) return libvlc_media_player_add_slave(m_player, libvlc_media_slave_type_subtitle, file.toUtf8().data(), true) == 0; #else return libvlc_video_set_subtitle_file(m_player, file.toUtf8().data()) == 1; #endif } void MediaPlayer::setTitle(int title) { libvlc_media_player_set_title(m_player, title); } void MediaPlayer::setChapter(int chapter) { libvlc_media_player_set_chapter(m_player, chapter); } QImage MediaPlayer::snapshot() const { - QTemporaryFile tempFile(QDir::tempPath() % QDir::separator() % QLatin1String("phonon-vlc-snapshot")); + QTemporaryFile tempFile(QDir::tempPath() % QDir::separator() % QLatin1Literal("phonon-vlc-snapshot")); tempFile.open(); // This function is sync. if (libvlc_video_take_snapshot(m_player, 0, tempFile.fileName().toLocal8Bit().data(), 0, 0) != 0) return QImage(); return QImage(tempFile.fileName()); } bool MediaPlayer::setAudioTrack(int track) { return libvlc_audio_set_track(m_player, track) == 0; } void MediaPlayer::event_cb(const libvlc_event_t *event, void *opaque) { MediaPlayer *that = reinterpret_cast(opaque); Q_ASSERT(that); // Do not forget to register for the events you want to handle here! switch (event->type) { case libvlc_MediaPlayerTimeChanged: QMetaObject::invokeMethod( that, "timeChanged", Qt::QueuedConnection, Q_ARG(qint64, event->u.media_player_time_changed.new_time)); break; case libvlc_MediaPlayerSeekableChanged: QMetaObject::invokeMethod( that, "seekableChanged", Qt::QueuedConnection, Q_ARG(bool, event->u.media_player_seekable_changed.new_seekable)); break; case libvlc_MediaPlayerLengthChanged: QMetaObject::invokeMethod( that, "lengthChanged", Qt::QueuedConnection, Q_ARG(qint64, event->u.media_player_length_changed.new_length)); break; case libvlc_MediaPlayerNothingSpecial: P_EMIT_STATE(NoState); break; case libvlc_MediaPlayerOpening: P_EMIT_STATE(OpeningState); break; case libvlc_MediaPlayerBuffering: QMetaObject::invokeMethod( that, "bufferChanged", Qt::QueuedConnection, Q_ARG(int, event->u.media_player_buffering.new_cache)); break; case libvlc_MediaPlayerPlaying: // Intercept state change and apply pausing once playing. if (that->m_doingPausedPlay) { that->m_doingPausedPlay = false; // VLC internally will call stop if a player can not be paused, this // can lead to deadlocks as stop is partially blocking, to avoid this // we explicitly do a queued stop whenever a player can not be paused. // In those situations pausedplay capability can not be provided, so // applications wanting to do pausedplay will need to handle it anyway // as faking a paused state when there is none would be a very code // intense workaround asking for weird abstraction leakage. // See kde bug 337604. if (libvlc_media_player_can_pause(that->m_player)) { that->pause(); } else { QMetaObject::invokeMethod(that, "stop", Qt::QueuedConnection); } } else P_EMIT_STATE(PlayingState); break; case libvlc_MediaPlayerPaused: P_EMIT_STATE(PausedState); break; case libvlc_MediaPlayerStopped: P_EMIT_STATE(StoppedState); break; case libvlc_MediaPlayerEndReached: P_EMIT_STATE(EndedState); break; case libvlc_MediaPlayerEncounteredError: P_EMIT_STATE(ErrorState); break; case libvlc_MediaPlayerVout: if (event->u.media_player_vout.new_count > 0) { P_EMIT_HAS_VIDEO(true); } else { P_EMIT_HAS_VIDEO(false); } break; case libvlc_MediaPlayerMediaChanged: break; #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 2, 0)) case libvlc_MediaPlayerCorked: that->pause(); break; case libvlc_MediaPlayerUncorked: that->play(); break; case libvlc_MediaPlayerMuted: QMetaObject::invokeMethod( that, "mutedChanged", Qt::QueuedConnection, Q_ARG(bool, true)); break; case libvlc_MediaPlayerUnmuted: QMetaObject::invokeMethod( that, "mutedChanged", Qt::QueuedConnection, Q_ARG(bool, false)); break; case libvlc_MediaPlayerAudioVolume: QMetaObject::invokeMethod( that, "volumeChanged", Qt::QueuedConnection, Q_ARG(float, event->u.media_player_audio_volume.volume)); break; #endif case libvlc_MediaPlayerForward: case libvlc_MediaPlayerBackward: case libvlc_MediaPlayerPositionChanged: case libvlc_MediaPlayerPausableChanged: case libvlc_MediaPlayerTitleChanged: case libvlc_MediaPlayerSnapshotTaken: // Snapshot call is sync, so this is useless. default: break; QString msg = QString("Unknown event: ") + QString::number(event->type); Q_ASSERT_X(false, "event_cb", qPrintable(msg)); break; } } QDebug operator<<(QDebug dbg, const MediaPlayer::State &s) { QString name; switch (s) { case MediaPlayer::NoState: name = QLatin1String("MediaPlayer::NoState"); break; case MediaPlayer::OpeningState: name = QLatin1String("MediaPlayer::OpeningState"); break; case MediaPlayer::BufferingState: name = QLatin1String("MediaPlayer::BufferingState"); break; case MediaPlayer::PlayingState: name = QLatin1String("MediaPlayer::PlayingState"); break; case MediaPlayer::PausedState: name = QLatin1String("MediaPlayer::PausedState"); break; case MediaPlayer::StoppedState: name = QLatin1String("MediaPlayer::StoppedState"); break; case MediaPlayer::EndedState: name = QLatin1String("MediaPlayer::EndedState"); break; case MediaPlayer::ErrorState: name = QLatin1String("MediaPlayer::ErrorState"); break; } dbg.nospace() << "State(" << qPrintable(name) << ")"; return dbg.space(); } void MediaPlayer::setAudioFade(qreal fade) { m_fadeAmount = fade; setVolumeInternal(); } void MediaPlayer::setAudioVolume(int volume) { m_volume = volume; setVolumeInternal(); } bool MediaPlayer::mute() const { return libvlc_audio_get_mute(m_player); } void MediaPlayer::setMute(bool mute) { libvlc_audio_set_mute(m_player, mute); } void MediaPlayer::setVolumeInternal() { libvlc_audio_set_volume(m_player, m_volume * m_fadeAmount); } void MediaPlayer::setCdTrack(int track) { if (!m_media) return; #ifdef __GNUC__ #warning changed to stop_async does this have impliciations #endif #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0)) libvlc_media_player_stop_async(m_player); #else libvlc_media_player_stop(m_player); #endif m_media->setCdTrack(track); libvlc_media_player_set_media(m_player, *m_media); libvlc_media_player_play(m_player); } #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 0, 0)) void MediaPlayer::setEqualizer(libvlc_equalizer_t *equalizer) { libvlc_media_player_set_equalizer(m_player, equalizer); } #endif } // namespace VLC } // namespace Phonon