1/*
2 Copyright (C) 2007-2008 Tanguy Krotoff <tkrotoff@gmail.com>
3 Copyright (C) 2008 Lukas Durfina <lukas.durfina@gmail.com>
4 Copyright (C) 2009 Fathi Boudra <fabo@kde.org>
5 Copyright (C) 2009-2011 vlc-phonon AUTHORS <kde-multimedia@kde.org>
6 Copyright (C) 2011-2013 Harald Sitter <sitter@kde.org>
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#include "backend.h"
23
24#include <QApplication>
25#include <QIcon>
26#include <QMessageBox>
27#include <QtPlugin>
28#include <QVariant>
29
30#include <phonon/GlobalDescriptionContainer>
31#include <phonon/pulsesupport.h>
32
33#include <vlc/libvlc_version.h>
34
35#include "audio/audiooutput.h"
36#include "audio/volumefadereffect.h"
37#include "config.h"
38#include "devicemanager.h"
39#include "effect.h"
40#include "effectmanager.h"
41#include "mediaobject.h"
42#include "sinknode.h"
43#include "utils/debug.h"
44#include "utils/libvlc.h"
45#include "utils/mime.h"
46#ifdef PHONON_EXPERIMENTAL
47#include "video/videodataoutput.h"
48#endif
49#include "video/videowidget.h"
50
51namespace Phonon
52{
53namespace VLC
54{
55
56Backend *Backend::self;
57
58Backend::Backend(QObject *parent, const QVariantList &)
59 : QObject(parent)
60 , m_deviceManager(0)
61 , m_effectManager(0)
62{
63 self = this;
64
65 // Backend information properties
66 setProperty(name: "identifier", value: QLatin1String("phonon_vlc"));
67 setProperty(name: "backendName", value: QLatin1String("VLC"));
68 setProperty(name: "backendComment", value: QLatin1String("VLC backend for Phonon"));
69 setProperty(name: "backendVersion", value: QLatin1String(PHONON_VLC_VERSION));
70 setProperty(name: "backendIcon", value: QLatin1String("vlc"));
71 setProperty(name: "backendWebsite", value: QLatin1String("https://commits.kde.org/phonon-vlc"));
72
73 // Check if we should enable debug output
74 int debugLevel = qgetenv(varName: "PHONON_BACKEND_DEBUG").toInt();
75 if (debugLevel > 3) // 3 is maximum
76 debugLevel = 3;
77 Debug::setMinimumDebugLevel((Debug::DebugLevel)((int) Debug::DEBUG_NONE - 1 - debugLevel));
78
79 debug() << "Constructing Phonon-VLC Version" << PHONON_VLC_VERSION;
80
81 // Actual libVLC initialisation
82 if (LibVLC::init()) {
83 debug() << "Using VLC version" << libvlc_get_version();
84 if (!qApp->applicationName().isEmpty()) {
85 QString userAgent =
86 QString("%0/%1 (Phonon/%2; Phonon-VLC/%3)").arg(
87 qApp->applicationName(),
88 qApp->applicationVersion(),
89 PHONON_VERSION_STR,
90 PHONON_VLC_VERSION);
91 libvlc_set_user_agent(pvlc_libvlc,
92 qApp->applicationName().toUtf8().constData(),
93 http: userAgent.toUtf8().constData());
94 } else {
95 qWarning(msg: "WARNING: Setting the user agent for streaming and"
96 " PulseAudio requires you to set QCoreApplication::applicationName()");
97 }
98
99 PulseSupport::getInstance()->enable(enabled: true);
100 const bool pulseActive = PulseSupport::getInstance()->isActive();
101 PulseSupport::getInstance()->enable(enabled: false);
102 if (!qApp->applicationName().isEmpty()) {
103 const QString id = QString("org.kde.phonon.%1").arg(qApp->applicationName());
104 const QString version = qApp->applicationVersion();
105 QString icon;
106 if (!qApp->windowIcon().isNull()){
107 // Try to get the fromTheme() name of the QIcon.
108 icon = qApp->windowIcon().name();
109 }
110 if (icon.isEmpty()) {
111 // If we failed to get a proper icon name, use the appname instead.
112 icon = qApp->applicationName().toLower();
113 }
114 libvlc_set_app_id(pvlc_libvlc,
115 id: id.toUtf8().constData(),
116 version: version.toUtf8().constData(),
117 icon: icon.toUtf8().constData());
118 } else if (pulseActive) {
119 qWarning(msg: "WARNING: Setting PulseAudio context information requires you"
120 " to set QCoreApplication::applicationName(),"
121 " QCoreApplication::applicationVersion() and"
122 " QGuiApplication::windowIcon().");
123 }
124 } else {
125#ifdef __GNUC__
126 #warning TODO - this error message is as useful as a knife at a gun fight
127#endif
128 QMessageBox msg;
129 msg.setIcon(QMessageBox::Critical);
130 msg.setWindowTitle(tr(s: "LibVLC Failed to Initialize"));
131 msg.setText(tr(s: "Phonon's VLC backend failed to start."
132 "\n\n"
133 "This usually means a problem with your VLC installation,"
134 " please report a bug with your distributor."));
135 msg.setDetailedText(LibVLC::errorMessage());
136 msg.exec();
137 fatal() << "Phonon::VLC::vlcInit: Failed to initialize VLC";
138 }
139
140 // Since VLC 2.2 PulseSupport is disabled since the "overlay" it implements clashes substantially with libvlc
141 // internals. Instead VLC has full control.
142
143 m_deviceManager = new DeviceManager(this);
144 m_effectManager = new EffectManager(this);
145}
146
147Backend::~Backend()
148{
149 if (LibVLC::self)
150 delete LibVLC::self;
151 if (GlobalAudioChannels::self)
152 delete GlobalAudioChannels::self;
153 if (GlobalSubtitles::self)
154 delete GlobalSubtitles::self;
155 PulseSupport::shutdown();
156}
157
158QObject *Backend::createObject(BackendInterface::Class c, QObject *parent, const QList<QVariant> &args)
159{
160 if (!LibVLC::self || !pvlc_libvlc)
161 return 0;
162
163 switch (c) {
164 case MediaObjectClass:
165 return new MediaObject(parent);
166 case AudioOutputClass:
167 return new AudioOutput(parent);
168 case AudioDataOutputClass:
169 // With VLC we can't have actual output and at the same time stream
170 // the data to memory. We therefore can't support ADO.
171 // https://trac.videolan.org/vlc/ticket/6992
172 return nullptr;
173#ifdef PHONON_EXPERIMENTAL
174 case VideoDataOutputClass:
175 return new VideoDataOutput(parent);
176#endif
177 case VideoGraphicsObjectClass:
178 return nullptr; // No longer supported
179 case EffectClass:
180 return effectManager()->createEffect(id: args[0].toInt(), parent);
181 case VideoWidgetClass:
182 return new VideoWidget(qobject_cast<QWidget *>(o: parent));
183// case VolumeFaderEffectClass:
184#ifdef __GNUC__
185#warning VFE crashes and has volume bugs ... deactivated
186// return new VolumeFaderEffect(parent);
187#endif
188 }
189
190 warning() << "Backend class" << c << "is not supported by Phonon VLC :(";
191 return 0;
192}
193
194QStringList Backend::availableMimeTypes() const
195{
196 if (m_supportedMimeTypes.isEmpty())
197 const_cast<Backend *>(this)->m_supportedMimeTypes = mimeTypeList();
198 return m_supportedMimeTypes;
199}
200
201QList<int> Backend::objectDescriptionIndexes(ObjectDescriptionType type) const
202{
203 QList<int> list;
204
205 switch (type) {
206 case Phonon::AudioChannelType: {
207 list << GlobalAudioChannels::instance()->globalIndexes();
208 }
209 break;
210 case Phonon::AudioOutputDeviceType:
211 case Phonon::AudioCaptureDeviceType:
212 case Phonon::VideoCaptureDeviceType: {
213 return deviceManager()->deviceIds(type);
214 }
215 break;
216 case Phonon::EffectType: {
217 QList<EffectInfo> effectList = effectManager()->effects();
218 for (int eff = 0; eff < effectList.size(); ++eff) {
219 list.append(t: eff);
220 }
221 }
222 break;
223 case Phonon::SubtitleType: {
224 list << GlobalSubtitles::instance()->globalIndexes();
225 }
226 break;
227 }
228
229 return list;
230}
231
232QHash<QByteArray, QVariant> Backend::objectDescriptionProperties(ObjectDescriptionType type, int index) const
233{
234 QHash<QByteArray, QVariant> ret;
235
236 switch (type) {
237 case Phonon::AudioChannelType: {
238 const AudioChannelDescription description = GlobalAudioChannels::instance()->fromIndex(key: index);
239 ret.insert(key: "name", value: description.name());
240 ret.insert(key: "description", value: description.description());
241 }
242 break;
243 case Phonon::AudioOutputDeviceType:
244 case Phonon::AudioCaptureDeviceType:
245 case Phonon::VideoCaptureDeviceType: {
246 // Index should be unique, even for different categories
247 return deviceManager()->deviceProperties(id: index);
248 }
249 break;
250 case Phonon::EffectType: {
251 const QList<EffectInfo> effectList = effectManager()->effects();
252 if (index >= 0 && index <= effectList.size()) {
253 const EffectInfo &effect = effectList.at(i: index);
254 ret.insert(key: "name", value: effect.name());
255 ret.insert(key: "description", value: effect.description());
256 ret.insert(key: "author", value: effect.author());
257 } else {
258 Q_ASSERT(1); // Since we use list position as ID, this should not happen
259 }
260 }
261 break;
262 case Phonon::SubtitleType: {
263 const SubtitleDescription description = GlobalSubtitles::instance()->fromIndex(key: index);
264 ret.insert(key: "name", value: description.name());
265 ret.insert(key: "description", value: description.description());
266 ret.insert(key: "type", value: description.property(name: "type"));
267 }
268 break;
269 }
270
271 return ret;
272}
273
274bool Backend::startConnectionChange(QSet<QObject *> objects)
275{
276 //FIXME
277 foreach(QObject * object, objects) {
278 debug() << "Object:" << object->metaObject()->className();
279 }
280
281 // There is nothing we can do but hope the connection changes will not take too long
282 // so that buffers would underrun
283 // But we should be pretty safe the way xine works by not doing anything here.
284 return true;
285}
286
287bool Backend::connectNodes(QObject *source, QObject *sink)
288{
289 debug() << "Backend connected" << source->metaObject()->className() << "to" << sink->metaObject()->className();
290
291 SinkNode *sinkNode = dynamic_cast<SinkNode *>(sink);
292 if (sinkNode) {
293 MediaObject *mediaObject = qobject_cast<MediaObject *>(object: source);
294 if (mediaObject) {
295 // Connect the SinkNode to a MediaObject
296 sinkNode->connectToMediaObject(mediaObject);
297 return true;
298 }
299
300 VolumeFaderEffect *effect = qobject_cast<VolumeFaderEffect *>(object: source);
301 if (effect) {
302 sinkNode->connectToMediaObject(mediaObject: effect->mediaObject());
303 return true;
304 }
305 }
306
307 warning() << "Linking" << source->metaObject()->className() << "to" << sink->metaObject()->className() << "failed";
308
309 return false;
310}
311
312bool Backend::disconnectNodes(QObject *source, QObject *sink)
313{
314 SinkNode *sinkNode = dynamic_cast<SinkNode *>(sink);
315 if (sinkNode) {
316 MediaObject *const mediaObject = qobject_cast<MediaObject *>(object: source);
317 if (mediaObject) {
318 // Disconnect the SinkNode from a MediaObject
319 sinkNode->disconnectFromMediaObject(mediaObject);
320 return true;
321 }
322
323 VolumeFaderEffect *const effect = qobject_cast<VolumeFaderEffect *>(object: source);
324 if (effect) {
325 sinkNode->disconnectFromMediaObject(mediaObject: effect->mediaObject());
326 return true;
327 }
328 }
329
330 return false;
331}
332
333bool Backend::endConnectionChange(QSet<QObject *> objects)
334{
335 foreach(QObject *object, objects) {
336 debug() << "Object:" << object->metaObject()->className();
337 }
338 return true;
339}
340
341DeviceManager *Backend::deviceManager() const
342{
343 return m_deviceManager;
344}
345
346EffectManager *Backend::effectManager() const
347{
348 return m_effectManager;
349}
350
351} // namespace VLC
352} // namespace Phonon
353

source code of phonon-vlc/src/backend.cpp