diff --git a/src/mediacontroller.cpp b/src/mediacontroller.cpp index 1435bb3..8a54cc2 100644 --- a/src/mediacontroller.cpp +++ b/src/mediacontroller.cpp @@ -1,476 +1,493 @@ /* 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-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 "mediacontroller.h" #include #include #include "utils/debug.h" #include "utils/libvlc.h" #include "mediaplayer.h" namespace Phonon { namespace VLC { #ifdef __GNUC__ #warning titles and chapters not covered by globaldescriptioncontainer!! #endif MediaController::MediaController() : m_subtitleAutodetect(true) , m_subtitleEncoding("UTF-8") , m_subtitleFontChanged(false) , m_player(0) , m_refreshTimer(new QTimer(dynamic_cast(this))) , m_attemptingAutoplay(false) { GlobalSubtitles::instance()->register_(this); GlobalAudioChannels::instance()->register_(this); resetMembers(); } MediaController::~MediaController() { GlobalSubtitles::instance()->unregister_(this); GlobalAudioChannels::instance()->unregister_(this); } bool MediaController::hasInterface(Interface iface) const { switch (iface) { case AddonInterface::NavigationInterface: return true; break; case AddonInterface::ChapterInterface: return true; break; case AddonInterface::AngleInterface: return false; break; case AddonInterface::TitleInterface: return true; break; case AddonInterface::SubtitleInterface: return true; break; case AddonInterface::AudioChannelInterface: return true; break; } warning() << "Interface" << iface << "is not supported by Phonon VLC :("; return false; } QVariant MediaController::interfaceCall(Interface iface, int i_command, const QList & arguments) { DEBUG_BLOCK; switch (iface) { case AddonInterface::ChapterInterface: switch (static_cast(i_command)) { case AddonInterface::availableChapters: return availableChapters(); case AddonInterface::chapter: return currentChapter(); case AddonInterface::setChapter: if (arguments.isEmpty() || !arguments.first().canConvert(QVariant::Int)) { error() << Q_FUNC_INFO << "arguments invalid"; return false; } setCurrentChapter(arguments.first().toInt()); return true; } break; case AddonInterface::TitleInterface: switch (static_cast(i_command)) { case AddonInterface::availableTitles: return availableTitles(); case AddonInterface::title: return currentTitle(); case AddonInterface::setTitle: if (arguments.isEmpty() || !arguments.first().canConvert(QVariant::Int)) { error() << Q_FUNC_INFO << "arguments invalid"; return false; } setCurrentTitle(arguments.first().toInt()); return true; case AddonInterface::autoplayTitles: return autoplayTitles(); case AddonInterface::setAutoplayTitles: if (arguments.isEmpty() || !arguments.first().canConvert(QVariant::Bool)) { error() << Q_FUNC_INFO << " arguments invalid"; return false; } setAutoplayTitles(arguments.first().toBool()); return true; } break; case AddonInterface::AngleInterface: warning() << "AddonInterface::AngleInterface not supported!"; break; case AddonInterface::SubtitleInterface: switch (static_cast(i_command)) { case AddonInterface::availableSubtitles: return QVariant::fromValue(availableSubtitles()); case AddonInterface::currentSubtitle: return QVariant::fromValue(currentSubtitle()); case AddonInterface::setCurrentSubtitle: if (arguments.isEmpty() || !arguments.first().canConvert()) { error() << Q_FUNC_INFO << "arguments invalid"; return false; } setCurrentSubtitle(arguments.first().value()); return true; case AddonInterface::setCurrentSubtitleFile: if (arguments.isEmpty() || !arguments.first().canConvert()) { error() << Q_FUNC_INFO << " arguments invalid"; return false; } setCurrentSubtitleFile(arguments.first().value()); case AddonInterface::subtitleAutodetect: return QVariant::fromValue(subtitleAutodetect()); case AddonInterface::setSubtitleAutodetect: if (arguments.isEmpty() || !arguments.first().canConvert()) { error() << Q_FUNC_INFO << " arguments invalid"; return false; } setSubtitleAutodetect(arguments.first().value()); return true; case AddonInterface::subtitleEncoding: return subtitleEncoding(); case AddonInterface::setSubtitleEncoding: if (arguments.isEmpty() || !arguments.first().canConvert()) { error() << Q_FUNC_INFO << " arguments invalid"; return false; } setSubtitleEncoding(arguments.first().value()); return true; case AddonInterface::subtitleFont: return subtitleFont(); case AddonInterface::setSubtitleFont: if (arguments.isEmpty() || !arguments.first().canConvert()) { error() << Q_FUNC_INFO << " arguments invalid"; return false; } setSubtitleFont(arguments.first().value()); return true; } break; case AddonInterface::AudioChannelInterface: switch (static_cast(i_command)) { case AddonInterface::availableAudioChannels: return QVariant::fromValue(availableAudioChannels()); case AddonInterface::currentAudioChannel: return QVariant::fromValue(currentAudioChannel()); case AddonInterface::setCurrentAudioChannel: if (arguments.isEmpty() || !arguments.first().canConvert()) { error() << Q_FUNC_INFO << "arguments invalid"; return false; } setCurrentAudioChannel(arguments.first().value()); return true; } break; } error() << Q_FUNC_INFO << "unsupported AddonInterface::Interface:" << iface; return QVariant(); } void MediaController::resetMediaController() { resetMembers(); emit availableAudioChannelsChanged(); emit availableSubtitlesChanged(); emit availableTitlesChanged(0); emit availableChaptersChanged(0); } void MediaController::resetMembers() { m_currentAudioChannel = Phonon::AudioChannelDescription(); GlobalAudioChannels::self->clearListFor(this); m_currentSubtitle = Phonon::SubtitleDescription(); GlobalSubtitles::instance()->clearListFor(this); m_currentChapter = 0; m_availableChapters = 0; m_currentTitle = 1; m_availableTitles = 0; m_attemptingAutoplay = false; } // ----------------------------- Audio Channel ------------------------------ // void MediaController::setCurrentAudioChannel(const Phonon::AudioChannelDescription &audioChannel) { const int localIndex = GlobalAudioChannels::instance()->localIdFor(this, audioChannel.index()); if (!m_player->setAudioTrack(localIndex)) error() << "libVLC:" << LibVLC::errorMessage(); else m_currentAudioChannel = audioChannel; } QList MediaController::availableAudioChannels() const { return GlobalAudioChannels::instance()->listFor(this); } Phonon::AudioChannelDescription MediaController::currentAudioChannel() const { return m_currentAudioChannel; } void MediaController::refreshAudioChannels() { GlobalAudioChannels::instance()->clearListFor(this); const int currentChannelId = m_player->subtitle(); int idCount = 0; VLC_FOREACH_TRACK(it, m_player->audioTrackDescription()) { // LibVLC's internal ID is broken, so we simply count up as internally // the setter will simply go by position in list anyway. GlobalAudioChannels::instance()->add(this, idCount, QString::fromUtf8(it->psz_name), ""); if (idCount == currentChannelId) { #ifdef __GNUC__ #warning GlobalDescriptionContainer does not allow reverse resolution from local to descriptor! #endif const QList list = GlobalAudioChannels::instance()->listFor(this); foreach (const AudioChannelDescription &descriptor, list) { if (descriptor.name() == QString::fromUtf8(it->psz_name)) { m_currentAudioChannel = descriptor; } } } ++idCount; } emit availableAudioChannelsChanged(); } // -------------------------------- Subtitle -------------------------------- // void MediaController::setCurrentSubtitle(const Phonon::SubtitleDescription &subtitle) { DEBUG_BLOCK; QString type = subtitle.property("type").toString(); debug() << subtitle; if (type == "file") { QString filename = subtitle.property("name").toString(); if (!filename.isEmpty()) { if (!m_player->setSubtitle(filename)) error() << "libVLC:" << LibVLC::errorMessage(); else m_currentSubtitle = subtitle; // There is no subtitle event inside libvlc so let's send our own event... GlobalSubtitles::instance()->add(this, m_currentSubtitle); emit availableSubtitlesChanged(); } } else { const int localIndex = GlobalSubtitles::instance()->localIdFor(this, subtitle.index()); debug () << "localid" << localIndex; if (!m_player->setSubtitle(localIndex)) error() << "libVLC:" << LibVLC::errorMessage(); else m_currentSubtitle = subtitle; } } void MediaController::setCurrentSubtitleFile(const QUrl &url) { const QString file = url.toLocalFile(); if (!m_player->setSubtitle(file)) error() << "libVLC failed to set subtitle file:" << LibVLC::errorMessage(); // Unfortunately the addition of SPUs does not trigger an event in the // VLC mediaplayer, yet the actual addition to the descriptor is async. // So for the time being our best shot at getting an up-to-date list of SPUs // is shooting in the dark and hoping we hit something. // Refresha after 1, 2 and 5 seconds. If we have no updated list after 5 // seconds we are out of luck. // https://trac.videolan.org/vlc/ticket/9796 QObject *mediaObject = dynamic_cast(this); // MediaObject : QObject, MediaController m_refreshTimer->singleShot(1 * 1000, mediaObject, SLOT(refreshDescriptors())); m_refreshTimer->singleShot(2 * 1000, mediaObject, SLOT(refreshDescriptors())); m_refreshTimer->singleShot(5 * 1000, mediaObject, SLOT(refreshDescriptors())); } QList MediaController::availableSubtitles() const { return GlobalSubtitles::instance()->listFor(this); } Phonon::SubtitleDescription MediaController::currentSubtitle() const { return m_currentSubtitle; } void MediaController::refreshSubtitles() { DEBUG_BLOCK; GlobalSubtitles::instance()->clearListFor(this); const int currentSubtitleId = m_player->subtitle(); VLC_FOREACH_TRACK(it, m_player->videoSubtitleDescription()) { debug() << "found subtitle" << it->psz_name << "[" << it->i_id << "]"; GlobalSubtitles::instance()->add(this, it->i_id, QString::fromUtf8(it->psz_name), ""); if (it->i_id == currentSubtitleId) { #ifdef __GNUC__ #warning GlobalDescriptionContainer does not allow reverse resolution from local to descriptor! #endif const QList list = GlobalSubtitles::instance()->listFor(this); foreach (const SubtitleDescription &descriptor, list) { if (descriptor.name() == QString::fromUtf8(it->psz_name)) { m_currentSubtitle = descriptor; } } } } emit availableSubtitlesChanged(); } bool MediaController::subtitleAutodetect() const { return m_subtitleAutodetect; } void MediaController::setSubtitleAutodetect(bool enabled) { m_subtitleAutodetect = enabled; } QString MediaController::subtitleEncoding() const { return m_subtitleEncoding; } void MediaController::setSubtitleEncoding(const QString &encoding) { m_subtitleEncoding = encoding; } QFont MediaController::subtitleFont() const { return m_subtitleFont; } void MediaController::setSubtitleFont(const QFont &font) { m_subtitleFontChanged = true; m_subtitleFont = font; } // --------------------------------- Title ---------------------------------- // void MediaController::setCurrentTitle(int title) { DEBUG_BLOCK; m_currentTitle = title; switch (source().discType()) { case Cd: m_player->setCdTrack(title); return; case Dvd: case Vcd: case BluRay: m_player->setTitle(title); return; case NoDisc: warning() << "Current media source is not a CD, DVD or VCD!"; return; } warning() << "MediaSource does not support setting of tile in this version of Phonon VLC!" << "Type is" << source().discType(); } int MediaController::availableTitles() const { return m_availableTitles; } int MediaController::currentTitle() const { return m_currentTitle; } void MediaController::setAutoplayTitles(bool autoplay) { m_autoPlayTitles = autoplay; } bool MediaController::autoplayTitles() const { return m_autoPlayTitles; } void MediaController::refreshTitles() { m_availableTitles = 0; +#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(3, 0, 0, 0)) + SharedTitleDescriptions list = m_player->titleDescription(); + for (unsigned int i = 0; i < list->size(); ++i) { + ++m_availableTitles; + emit availableTitlesChanged(m_availableTitles); + } +#else VLC_FOREACH_TRACK(it, m_player->titleDescription()) { ++m_availableTitles; emit availableTitlesChanged(m_availableTitles); } +#endif } // -------------------------------- Chapter --------------------------------- // void MediaController::setCurrentChapter(int chapter) { m_currentChapter = chapter; m_player->setChapter(chapter); } int MediaController::availableChapters() const { return m_availableChapters; } int MediaController::currentChapter() const { return m_currentChapter; } // We need to rebuild available chapters when title is changed void MediaController::refreshChapters(int title) { m_availableChapters = 0; // Get the description of available chapters for specific title +#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(3, 0, 0, 0)) + SharedChapterDescriptions list = m_player->videoChapterDescription(title); + for (unsigned int i = 0; i < list->size(); ++i) { + ++m_availableChapters; + emit availableChaptersChanged(m_availableChapters); + } +#else VLC_FOREACH_TRACK(it, m_player->videoChapterDescription(title)) { ++m_availableChapters; emit availableChaptersChanged(m_availableChapters); } +#endif } // --------------------------------- Angle ---------------------------------- // // NOT SUPPORTED IN LIBVLC // } // namespace VLC } // namespace Phonon diff --git a/src/mediaplayer.h b/src/mediaplayer.h index 167be8e..9d413d4 100644 --- a/src/mediaplayer.h +++ b/src/mediaplayer.h @@ -1,211 +1,275 @@ /* - Copyright (C) 2011 Harald Sitter + 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 . */ #ifndef PHONON_VLC_MEDIAPLAYER_H #define PHONON_VLC_MEDIAPLAYER_H -#include -#include +#include +#include +#include #include #include class QImage; class QString; namespace Phonon { namespace VLC { class Media; +template +class Descriptions +{ +public: + typedef void (*ReleaseFunction) (VLCArray**, unsigned int) ; + + Descriptions(VLCArray **data, + unsigned int size, + ReleaseFunction release) + : m_release(release) + , m_data(data) + , m_size(size) + { + } + + ~Descriptions() + { + m_release(m_data, m_size); + } + + unsigned int size() const { return m_size; } + +private: + ReleaseFunction m_release; + + VLCArray **m_data; + unsigned int m_size; +}; + +typedef Descriptions TitleDescriptions; +typedef QSharedPointer SharedTitleDescriptions; + +typedef Descriptions ChapterDescriptions; +typedef QSharedPointer SharedChapterDescriptions; + class MediaPlayer : public QObject { Q_OBJECT public: enum State { NoState = 0, OpeningState, BufferingState, PlayingState, PausedState, StoppedState, EndedState, ErrorState }; explicit MediaPlayer(QObject *parent = 0); ~MediaPlayer(); inline libvlc_media_player_t *libvlc_media_player() const { return m_player; } inline operator libvlc_media_player_t *() const { return m_player; } void setMedia(Media *media); void setVideoCallbacks(); void setVideoFormatCallbacks(); void setNsObject(void *drawable) { libvlc_media_player_set_nsobject(m_player, drawable); } void setXWindow(quint32 drawable) { libvlc_media_player_set_xwindow(m_player, drawable); } void setHwnd(void *drawable) { libvlc_media_player_set_hwnd(m_player, drawable); } // Playback bool play(); void pause(); void pausedPlay(); void resume(); void togglePause(); Q_INVOKABLE void stop(); qint64 length() const; qint64 time() const; void setTime(qint64 newTime); bool isSeekable() const; // Video QSize videoSize() const { unsigned int width; unsigned int height; libvlc_video_get_size(m_player, 0, &width, &height); return QSize(width, height); } bool hasVideoOutput() const; /// Set new video aspect ratio. /// \param aspect new video aspect-ratio or empty to reset to default void setVideoAspectRatio(const QByteArray &aspect) { libvlc_video_set_aspect_ratio(m_player, aspect.isEmpty() ? 0 : aspect.data()); } void setVideoAdjust(libvlc_video_adjust_option_t adjust, int value) { libvlc_video_set_adjust_int(m_player, adjust, value); } void setVideoAdjust(libvlc_video_adjust_option_t adjust, float value) { libvlc_video_set_adjust_float(m_player, adjust, value); } int subtitle() const { return libvlc_video_get_spu(m_player); } libvlc_track_description_t *videoSubtitleDescription() const { return libvlc_video_get_spu_description(m_player); } bool setSubtitle(int subtitle); bool setSubtitle(const QString &file); int title() const { return libvlc_media_player_get_title(m_player); } int titleCount() const { return libvlc_media_player_get_title_count(m_player); } +#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(3, 0, 0, 0)) + SharedTitleDescriptions titleDescription() const + { + libvlc_title_description_t **data; + unsigned int size = + libvlc_media_player_get_full_title_descriptions(m_player, &data); + return SharedTitleDescriptions( + new TitleDescriptions( + data, size, + &libvlc_title_descriptions_release) + ); + } +#else // deprecated libvlc_track_description_t *titleDescription() const { return libvlc_video_get_title_description(m_player); } +#endif void setTitle(int title); int videoChapterCount() const { return libvlc_media_player_get_chapter_count(m_player); } +#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(3, 0, 0, 0)) + SharedChapterDescriptions videoChapterDescription(int title) const + { + libvlc_chapter_description_t **data; + unsigned int size = + libvlc_media_player_get_full_chapter_descriptions(m_player, title, &data); + return SharedChapterDescriptions( + new ChapterDescriptions( + data, size, + &libvlc_chapter_descriptions_release) + ); + } +#else // deprecated libvlc_track_description_t *videoChapterDescription(int title) const { return libvlc_video_get_chapter_description(m_player, title); } +#endif void setChapter(int chapter); /** Reentrant, through libvlc */ QImage snapshot() const; // Audio /// Get current audio volume. /// \return the software volume in percents (0 = mute, 100 = nominal / 0dB) int audioVolume() const { return m_volume; } /// Set new audio volume. /// \param volume new volume void setAudioVolume(int volume); /// \return mutness bool mute() const; /** * Mutes * @param mute whether to mute or unmute */ void setMute(bool mute); /// Set the fade percentage, between 0 (muted) and 1.0 (no fade) void setAudioFade(qreal fade); /// \param name name of the output to set /// \returns \c true when setting was successful, \c false otherwise bool setAudioOutput(const QByteArray &name) { return libvlc_audio_output_set(m_player, name.data()) == 0; } /** * Set audio output device by name. * \param outputName the aout name (pulse, alsa, oss, etc.) * \param deviceName the output name (aout dependent) */ void setAudioOutputDevice(const QByteArray &outputName, const QByteArray &deviceName) { libvlc_audio_output_device_set(m_player, outputName.data(), deviceName.data()); } int audioTrack() const { return libvlc_audio_get_track(m_player); } libvlc_track_description_t * audioTrackDescription() const { return libvlc_audio_get_track_description(m_player); } bool setAudioTrack(int track); void setCdTrack(int track); #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 0, 0)) void setEqualizer(libvlc_equalizer_t *equalizer); #endif signals: void lengthChanged(qint64 length); void seekableChanged(bool seekable); void stateChanged(MediaPlayer::State state); void timeChanged(qint64 time); void bufferChanged(int percent); /** Emitted when the vout availability has changed */ void hasVideoChanged(bool hasVideo); void mutedChanged(bool mute); void volumeChanged(float volume); private: static void event_cb(const libvlc_event_t *event, void *opaque); void setVolumeInternal(); Media *m_media; libvlc_media_player_t *m_player; bool m_doingPausedPlay; int m_volume; qreal m_fadeAmount; }; QDebug operator<<(QDebug dbg, const MediaPlayer::State &s); } // namespace VLC } // namespace Phonon #endif // PHONON_VLC_MEDIAPLAYER_H