diff --git a/src/mediaobject.cpp b/src/mediaobject.cpp index 6d5f470..18e5433 100644 --- a/src/mediaobject.cpp +++ b/src/mediaobject.cpp @@ -1,821 +1,821 @@ /* 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(), + // 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(QLatin1Literal("cdda://") % m_mediaSource.deviceName()); break; case Phonon::Dvd: loadMedia(QLatin1Literal("dvd://") % m_mediaSource.deviceName()); break; case Phonon::Vcd: loadMedia(QLatin1Literal("vcd://") % m_mediaSource.deviceName()); break; case Phonon::BluRay: 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(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(QLatin1Literal("alsa://") % deviceName); } else if (driverName == "screen") { 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 { return 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 c006310..53aecd9 100644 --- a/src/mediaplayer.cpp +++ b/src/mediaplayer.cpp @@ -1,405 +1,406 @@ /* Copyright (C) 2011-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 "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; libvlc_media_player_stop(m_player); } 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) { libvlc_media_player_set_time(m_player, newTime); } 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) { return libvlc_video_set_subtitle_file(m_player, file.toUtf8().data()) == 1; } 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() % 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) + if (event->u.media_player_vout.new_count > 0) { P_EMIT_HAS_VIDEO(true); - else + } 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(libvlc_event_type_name(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; libvlc_media_player_stop(m_player); 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