File indexing completed on 2024-04-28 04:43:19

0001 /*
0002     Copyright (C) 2007-2008 Tanguy Krotoff <tkrotoff@gmail.com>
0003     Copyright (C) 2008 Lukas Durfina <lukas.durfina@gmail.com>
0004     Copyright (C) 2009 Fathi Boudra <fabo@kde.org>
0005     Copyright (C) 2009-2011 vlc-phonon AUTHORS <kde-multimedia@kde.org>
0006     Copyright (C) 2011-2013 Harald Sitter <sitter@kde.org>
0007 
0008     This library is free software; you can redistribute it and/or
0009     modify it under the terms of the GNU Lesser General Public
0010     License as published by the Free Software Foundation; either
0011     version 2.1 of the License, or (at your option) any later version.
0012 
0013     This library is distributed in the hope that it will be useful,
0014     but WITHOUT ANY WARRANTY; without even the implied warranty of
0015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016     Lesser General Public License for more details.
0017 
0018     You should have received a copy of the GNU Lesser General Public
0019     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0020 */
0021 
0022 #include "backend.h"
0023 
0024 #include <QApplication>
0025 #include <QIcon>
0026 #include <QMessageBox>
0027 #include <QtPlugin>
0028 #include <QVariant>
0029 
0030 #include <phonon/GlobalDescriptionContainer>
0031 #include <phonon/pulsesupport.h>
0032 
0033 #include <vlc/libvlc_version.h>
0034 
0035 #include "audio/audiooutput.h"
0036 #include "audio/volumefadereffect.h"
0037 #include "config.h"
0038 #include "devicemanager.h"
0039 #include "effect.h"
0040 #include "effectmanager.h"
0041 #include "mediaobject.h"
0042 #include "sinknode.h"
0043 #include "utils/debug.h"
0044 #include "utils/libvlc.h"
0045 #include "utils/mime.h"
0046 #ifdef PHONON_EXPERIMENTAL
0047 #include "video/videodataoutput.h"
0048 #endif
0049 #include "video/videowidget.h"
0050 
0051 namespace Phonon
0052 {
0053 namespace VLC
0054 {
0055 
0056 Backend *Backend::self;
0057 
0058 Backend::Backend(QObject *parent, const QVariantList &)
0059     : QObject(parent)
0060     , m_deviceManager(0)
0061     , m_effectManager(0)
0062 {
0063     self = this;
0064 
0065     // Backend information properties
0066     setProperty("identifier",     QLatin1String("phonon_vlc"));
0067     setProperty("backendName",    QLatin1String("VLC"));
0068     setProperty("backendComment", QLatin1String("VLC backend for Phonon"));
0069     setProperty("backendVersion", QLatin1String(PHONON_VLC_VERSION));
0070     setProperty("backendIcon",    QLatin1String("vlc"));
0071     setProperty("backendWebsite", QLatin1String("https://commits.kde.org/phonon-vlc"));
0072 
0073     // Check if we should enable debug output
0074     int debugLevel = qgetenv("PHONON_BACKEND_DEBUG").toInt();
0075     if (debugLevel > 3) // 3 is maximum
0076         debugLevel = 3;
0077     Debug::setMinimumDebugLevel((Debug::DebugLevel)((int) Debug::DEBUG_NONE - 1 - debugLevel));
0078 
0079     debug() << "Constructing Phonon-VLC Version" << PHONON_VLC_VERSION;
0080 
0081     // Actual libVLC initialisation
0082     if (LibVLC::init()) {
0083         debug() << "Using VLC version" << libvlc_get_version();
0084         if (!qApp->applicationName().isEmpty()) {
0085             QString userAgent =
0086                     QString("%0/%1 (Phonon/%2; Phonon-VLC/%3)").arg(
0087                         qApp->applicationName(),
0088                         qApp->applicationVersion(),
0089                         PHONON_VERSION_STR,
0090                         PHONON_VLC_VERSION);
0091             libvlc_set_user_agent(pvlc_libvlc,
0092                                   qApp->applicationName().toUtf8().constData(),
0093                                   userAgent.toUtf8().constData());
0094         } else {
0095             qWarning("WARNING: Setting the user agent for streaming and"
0096                      " PulseAudio requires you to set QCoreApplication::applicationName()");
0097         }
0098 
0099         PulseSupport::getInstance()->enable(true);
0100         const bool pulseActive = PulseSupport::getInstance()->isActive();
0101         PulseSupport::getInstance()->enable(false);
0102         if (!qApp->applicationName().isEmpty()) {
0103             const QString id = QString("org.kde.phonon.%1").arg(qApp->applicationName());
0104             const QString version = qApp->applicationVersion();
0105             QString icon;
0106             if (!qApp->windowIcon().isNull()){
0107                 // Try to get the fromTheme() name of the QIcon.
0108                 icon = qApp->windowIcon().name();
0109             }
0110             if (icon.isEmpty()) {
0111                 // If we failed to get a proper icon name, use the appname instead.
0112                 icon = qApp->applicationName().toLower();
0113             }
0114             libvlc_set_app_id(pvlc_libvlc,
0115                               id.toUtf8().constData(),
0116                               version.toUtf8().constData(),
0117                               icon.toUtf8().constData());
0118         } else if (pulseActive) {
0119             qWarning("WARNING: Setting PulseAudio context information requires you"
0120                      " to set QCoreApplication::applicationName(),"
0121                      " QCoreApplication::applicationVersion() and"
0122                      " QGuiApplication::windowIcon().");
0123         }
0124     } else {
0125 #ifdef __GNUC__
0126     #warning TODO - this error message is as useful as a knife at a gun fight
0127 #endif
0128         QMessageBox msg;
0129         msg.setIcon(QMessageBox::Critical);
0130         msg.setWindowTitle(tr("LibVLC Failed to Initialize"));
0131         msg.setText(tr("Phonon's VLC backend failed to start."
0132                        "\n\n"
0133                        "This usually means a problem with your VLC installation,"
0134                        " please report a bug with your distributor."));
0135         msg.setDetailedText(LibVLC::errorMessage());
0136         msg.exec();
0137         fatal() << "Phonon::VLC::vlcInit: Failed to initialize VLC";
0138     }
0139 
0140     // Since VLC 2.2 PulseSupport is disabled since the "overlay" it implements clashes substantially with libvlc
0141     // internals. Instead VLC has full control.
0142 
0143     m_deviceManager = new DeviceManager(this);
0144     m_effectManager = new EffectManager(this);
0145 }
0146 
0147 Backend::~Backend()
0148 {
0149     if (LibVLC::self)
0150         delete LibVLC::self;
0151     if (GlobalAudioChannels::self)
0152         delete GlobalAudioChannels::self;
0153     if (GlobalSubtitles::self)
0154         delete GlobalSubtitles::self;
0155     PulseSupport::shutdown();
0156 }
0157 
0158 QObject *Backend::createObject(BackendInterface::Class c, QObject *parent, const QList<QVariant> &args)
0159 {
0160     if (!LibVLC::self || !pvlc_libvlc)
0161         return 0;
0162 
0163     switch (c) {
0164     case MediaObjectClass:
0165         return new MediaObject(parent);
0166     case AudioOutputClass:
0167         return new AudioOutput(parent);
0168     case AudioDataOutputClass:
0169         // With VLC we can't have actual output and at the same time stream
0170         // the data to memory. We therefore can't support ADO.
0171         // https://trac.videolan.org/vlc/ticket/6992
0172         return nullptr;
0173 #ifdef PHONON_EXPERIMENTAL
0174     case VideoDataOutputClass:
0175         return new VideoDataOutput(parent);
0176 #endif
0177     case VideoGraphicsObjectClass:
0178         return nullptr; // No longer supported
0179     case EffectClass:
0180         return effectManager()->createEffect(args[0].toInt(), parent);
0181     case VideoWidgetClass:
0182         return new VideoWidget(qobject_cast<QWidget *>(parent));
0183 //    case VolumeFaderEffectClass:
0184 #ifdef __GNUC__
0185 #warning VFE crashes and has volume bugs ... deactivated
0186 //        return new VolumeFaderEffect(parent);
0187 #endif
0188     }
0189 
0190     warning() << "Backend class" << c << "is not supported by Phonon VLC :(";
0191     return 0;
0192 }
0193 
0194 QStringList Backend::availableMimeTypes() const
0195 {
0196     if (m_supportedMimeTypes.isEmpty())
0197         const_cast<Backend *>(this)->m_supportedMimeTypes = mimeTypeList();
0198     return m_supportedMimeTypes;
0199 }
0200 
0201 QList<int> Backend::objectDescriptionIndexes(ObjectDescriptionType type) const
0202 {
0203     QList<int> list;
0204 
0205     switch (type) {
0206     case Phonon::AudioChannelType: {
0207         list << GlobalAudioChannels::instance()->globalIndexes();
0208     }
0209     break;
0210     case Phonon::AudioOutputDeviceType:
0211     case Phonon::AudioCaptureDeviceType:
0212     case Phonon::VideoCaptureDeviceType: {
0213         return deviceManager()->deviceIds(type);
0214     }
0215     break;
0216     case Phonon::EffectType: {
0217         QList<EffectInfo> effectList = effectManager()->effects();
0218         for (int eff = 0; eff < effectList.size(); ++eff) {
0219             list.append(eff);
0220         }
0221     }
0222     break;
0223     case Phonon::SubtitleType: {
0224         list << GlobalSubtitles::instance()->globalIndexes();
0225     }
0226     break;
0227     }
0228 
0229     return list;
0230 }
0231 
0232 QHash<QByteArray, QVariant> Backend::objectDescriptionProperties(ObjectDescriptionType type, int index) const
0233 {
0234     QHash<QByteArray, QVariant> ret;
0235 
0236     switch (type) {
0237     case Phonon::AudioChannelType: {
0238         const AudioChannelDescription description = GlobalAudioChannels::instance()->fromIndex(index);
0239         ret.insert("name", description.name());
0240         ret.insert("description", description.description());
0241     }
0242     break;
0243     case Phonon::AudioOutputDeviceType:
0244     case Phonon::AudioCaptureDeviceType:
0245     case Phonon::VideoCaptureDeviceType: {
0246         // Index should be unique, even for different categories
0247         return deviceManager()->deviceProperties(index);
0248     }
0249     break;
0250     case Phonon::EffectType: {
0251         const QList<EffectInfo> effectList = effectManager()->effects();
0252         if (index >= 0 && index <= effectList.size()) {
0253             const EffectInfo &effect = effectList.at(index);
0254             ret.insert("name", effect.name());
0255             ret.insert("description", effect.description());
0256             ret.insert("author", effect.author());
0257         } else {
0258             Q_ASSERT(1); // Since we use list position as ID, this should not happen
0259         }
0260     }
0261     break;
0262     case Phonon::SubtitleType: {
0263         const SubtitleDescription description = GlobalSubtitles::instance()->fromIndex(index);
0264         ret.insert("name", description.name());
0265         ret.insert("description", description.description());
0266         ret.insert("type", description.property("type"));
0267     }
0268     break;
0269     }
0270 
0271     return ret;
0272 }
0273 
0274 bool Backend::startConnectionChange(QSet<QObject *> objects)
0275 {
0276     //FIXME
0277     foreach(QObject * object, objects) {
0278         debug() << "Object:" << object->metaObject()->className();
0279     }
0280 
0281     // There is nothing we can do but hope the connection changes will not take too long
0282     // so that buffers would underrun
0283     // But we should be pretty safe the way xine works by not doing anything here.
0284     return true;
0285 }
0286 
0287 bool Backend::connectNodes(QObject *source, QObject *sink)
0288 {
0289     debug() << "Backend connected" << source->metaObject()->className() << "to" << sink->metaObject()->className();
0290 
0291     SinkNode *sinkNode = dynamic_cast<SinkNode *>(sink);
0292     if (sinkNode) {
0293         MediaObject *mediaObject = qobject_cast<MediaObject *>(source);
0294         if (mediaObject) {
0295             // Connect the SinkNode to a MediaObject
0296             sinkNode->connectToMediaObject(mediaObject);
0297             return true;
0298         }
0299 
0300         VolumeFaderEffect *effect = qobject_cast<VolumeFaderEffect *>(source);
0301         if (effect) {
0302             sinkNode->connectToMediaObject(effect->mediaObject());
0303             return true;
0304         }
0305     }
0306 
0307     warning() << "Linking" << source->metaObject()->className() << "to" << sink->metaObject()->className() << "failed";
0308 
0309     return false;
0310 }
0311 
0312 bool Backend::disconnectNodes(QObject *source, QObject *sink)
0313 {
0314     SinkNode *sinkNode = dynamic_cast<SinkNode *>(sink);
0315     if (sinkNode) {
0316         MediaObject *const mediaObject = qobject_cast<MediaObject *>(source);
0317         if (mediaObject) {
0318             // Disconnect the SinkNode from a MediaObject
0319             sinkNode->disconnectFromMediaObject(mediaObject);
0320             return true;
0321         }
0322 
0323         VolumeFaderEffect *const effect = qobject_cast<VolumeFaderEffect *>(source);
0324         if (effect) {
0325             sinkNode->disconnectFromMediaObject(effect->mediaObject());
0326             return true;
0327         }
0328     }
0329 
0330     return false;
0331 }
0332 
0333 bool Backend::endConnectionChange(QSet<QObject *> objects)
0334 {
0335     foreach(QObject *object, objects) {
0336         debug() << "Object:" << object->metaObject()->className();
0337     }
0338     return true;
0339 }
0340 
0341 DeviceManager *Backend::deviceManager() const
0342 {
0343     return m_deviceManager;
0344 }
0345 
0346 EffectManager *Backend::effectManager() const
0347 {
0348     return m_effectManager;
0349 }
0350 
0351 } // namespace VLC
0352 } // namespace Phonon