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-2018 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 "mediacontroller.h"
23
24#include <phonon/GlobalDescriptionContainer>
25
26#include <QTimer>
27
28#include "utils/debug.h"
29#include "utils/libvlc.h"
30#include "mediaplayer.h"
31
32namespace Phonon {
33namespace VLC {
34
35#ifdef __GNUC__
36#warning titles and chapters not covered by globaldescriptioncontainer!!
37#endif
38
39MediaController::MediaController()
40 : m_subtitleAutodetect(true)
41 , m_subtitleEncoding("UTF-8")
42 , m_subtitleFontChanged(false)
43 , m_player(0)
44 , m_refreshTimer(new QTimer(dynamic_cast<QObject *>(this)))
45 , m_attemptingAutoplay(false)
46{
47 GlobalSubtitles::instance()->register_(obj: this);
48 GlobalAudioChannels::instance()->register_(obj: this);
49 resetMembers();
50}
51
52MediaController::~MediaController()
53{
54 GlobalSubtitles::instance()->unregister_(obj: this);
55 GlobalAudioChannels::instance()->unregister_(obj: this);
56}
57
58bool MediaController::hasInterface(Interface iface) const
59{
60 switch (iface) {
61 case AddonInterface::NavigationInterface:
62 return true;
63 break;
64 case AddonInterface::ChapterInterface:
65 return true;
66 break;
67 case AddonInterface::AngleInterface:
68 return false;
69 break;
70 case AddonInterface::TitleInterface:
71 return true;
72 break;
73 case AddonInterface::SubtitleInterface:
74 return true;
75 break;
76 case AddonInterface::AudioChannelInterface:
77 return true;
78 break;
79 }
80
81 warning() << "Interface" << iface << "is not supported by Phonon VLC :(";
82 return false;
83}
84
85QVariant MediaController::interfaceCall(Interface iface, int i_command, const QList<QVariant> & arguments)
86{
87 DEBUG_BLOCK;
88 switch (iface) {
89 case AddonInterface::ChapterInterface:
90 switch (static_cast<AddonInterface::ChapterCommand>(i_command)) {
91 case AddonInterface::availableChapters:
92 return availableChapters();
93 case AddonInterface::chapter:
94 return currentChapter();
95 case AddonInterface::setChapter:
96 if (arguments.isEmpty() || !arguments.first().canConvert(targetTypeId: QVariant::Int)) {
97 error() << Q_FUNC_INFO << "arguments invalid";
98 return false;
99 }
100 setCurrentChapter(arguments.first().toInt());
101 return true;
102 }
103 break;
104 case AddonInterface::TitleInterface:
105 switch (static_cast<AddonInterface::TitleCommand>(i_command)) {
106 case AddonInterface::availableTitles:
107 return availableTitles();
108 case AddonInterface::title:
109 return currentTitle();
110 case AddonInterface::setTitle:
111 if (arguments.isEmpty() || !arguments.first().canConvert(targetTypeId: QVariant::Int)) {
112 error() << Q_FUNC_INFO << "arguments invalid";
113 return false;
114 }
115 setCurrentTitle(arguments.first().toInt());
116 return true;
117 case AddonInterface::autoplayTitles:
118 return autoplayTitles();
119 case AddonInterface::setAutoplayTitles:
120 if (arguments.isEmpty() || !arguments.first().canConvert(targetTypeId: QVariant::Bool)) {
121 error() << Q_FUNC_INFO << " arguments invalid";
122 return false;
123 }
124 setAutoplayTitles(arguments.first().toBool());
125 return true;
126 }
127 break;
128 case AddonInterface::AngleInterface:
129 warning() << "AddonInterface::AngleInterface not supported!";
130 break;
131 case AddonInterface::SubtitleInterface:
132 switch (static_cast<AddonInterface::SubtitleCommand>(i_command)) {
133 case AddonInterface::availableSubtitles:
134 return QVariant::fromValue(value: availableSubtitles());
135 case AddonInterface::currentSubtitle:
136 return QVariant::fromValue(value: currentSubtitle());
137 case AddonInterface::setCurrentSubtitle:
138 if (arguments.isEmpty() || !arguments.first().canConvert<SubtitleDescription>()) {
139 error() << Q_FUNC_INFO << "arguments invalid";
140 return false;
141 }
142 setCurrentSubtitle(arguments.first().value<SubtitleDescription>());
143 return true;
144 case AddonInterface::setCurrentSubtitleFile:
145 if (arguments.isEmpty() || !arguments.first().canConvert<QUrl>()) {
146 error() << Q_FUNC_INFO << " arguments invalid";
147 return false;
148 }
149 setCurrentSubtitleFile(arguments.first().value<QUrl>());
150 case AddonInterface::subtitleAutodetect:
151 return QVariant::fromValue(value: subtitleAutodetect());
152 case AddonInterface::setSubtitleAutodetect:
153 if (arguments.isEmpty() || !arguments.first().canConvert<bool>()) {
154 error() << Q_FUNC_INFO << " arguments invalid";
155 return false;
156 }
157 setSubtitleAutodetect(arguments.first().value<bool>());
158 return true;
159 case AddonInterface::subtitleEncoding:
160 return subtitleEncoding();
161 case AddonInterface::setSubtitleEncoding:
162 if (arguments.isEmpty() || !arguments.first().canConvert<QString>()) {
163 error() << Q_FUNC_INFO << " arguments invalid";
164 return false;
165 }
166 setSubtitleEncoding(arguments.first().value<QString>());
167 return true;
168 case AddonInterface::subtitleFont:
169 return subtitleFont();
170 case AddonInterface::setSubtitleFont:
171 if (arguments.isEmpty() || !arguments.first().canConvert<QFont>()) {
172 error() << Q_FUNC_INFO << " arguments invalid";
173 return false;
174 }
175 setSubtitleFont(arguments.first().value<QFont>());
176 return true;
177 }
178 break;
179 case AddonInterface::AudioChannelInterface:
180 switch (static_cast<AddonInterface::AudioChannelCommand>(i_command)) {
181 case AddonInterface::availableAudioChannels:
182 return QVariant::fromValue(value: availableAudioChannels());
183 case AddonInterface::currentAudioChannel:
184 return QVariant::fromValue(value: currentAudioChannel());
185 case AddonInterface::setCurrentAudioChannel:
186 if (arguments.isEmpty() || !arguments.first().canConvert<AudioChannelDescription>()) {
187 error() << Q_FUNC_INFO << "arguments invalid";
188 return false;
189 }
190 setCurrentAudioChannel(arguments.first().value<AudioChannelDescription>());
191 return true;
192 }
193 break;
194 }
195
196 error() << Q_FUNC_INFO << "unsupported AddonInterface::Interface:" << iface;
197
198 return QVariant();
199}
200
201void MediaController::resetMediaController()
202{
203 resetMembers();
204 emit availableAudioChannelsChanged();
205 emit availableSubtitlesChanged();
206 emit availableTitlesChanged(0);
207 emit availableChaptersChanged(0);
208}
209
210void MediaController::resetMembers()
211{
212 m_currentAudioChannel = Phonon::AudioChannelDescription();
213 GlobalAudioChannels::self->clearListFor(obj: this);
214
215 m_currentSubtitle = Phonon::SubtitleDescription();
216 GlobalSubtitles::instance()->clearListFor(obj: this);
217
218 m_currentChapter = 0;
219 m_availableChapters = 0;
220
221 m_currentTitle = 1;
222 m_availableTitles = 0;
223
224 m_attemptingAutoplay = false;
225}
226
227// ----------------------------- Audio Channel ------------------------------ //
228void MediaController::setCurrentAudioChannel(const Phonon::AudioChannelDescription &audioChannel)
229{
230 const int localIndex = GlobalAudioChannels::instance()->localIdFor(obj: this, key: audioChannel.index());
231 if (!m_player->setAudioTrack(localIndex))
232 error() << "libVLC:" << LibVLC::errorMessage();
233 else
234 m_currentAudioChannel = audioChannel;
235}
236
237QList<Phonon::AudioChannelDescription> MediaController::availableAudioChannels() const
238{
239 return GlobalAudioChannels::instance()->listFor(obj: this);
240}
241
242Phonon::AudioChannelDescription MediaController::currentAudioChannel() const
243{
244 return m_currentAudioChannel;
245}
246
247void MediaController::refreshAudioChannels()
248{
249 GlobalAudioChannels::instance()->clearListFor(obj: this);
250
251 const int currentChannelId = m_player->audioTrack();
252
253 int idCount = 0;
254 VLC_FOREACH_TRACK(it, m_player->audioTrackDescription()) {
255 idCount= it->i_id;
256 GlobalAudioChannels::instance()->add(obj: this, index: idCount, name: QString::fromUtf8(utf8: it->psz_name), type: "");
257 if (idCount == currentChannelId) {
258#ifdef __GNUC__
259#warning GlobalDescriptionContainer does not allow reverse resolution from local to descriptor!
260#endif
261 const QList<AudioChannelDescription> list = GlobalAudioChannels::instance()->listFor(obj: this);
262 foreach (const AudioChannelDescription &descriptor, list) {
263 if (descriptor.name() == QString::fromUtf8(utf8: it->psz_name)) {
264 m_currentAudioChannel = descriptor;
265 }
266 }
267 }
268 ++idCount;
269 }
270
271 emit availableAudioChannelsChanged();
272}
273
274// -------------------------------- Subtitle -------------------------------- //
275void MediaController::setCurrentSubtitle(const Phonon::SubtitleDescription &subtitle)
276{
277 DEBUG_BLOCK;
278 QString type = subtitle.property(name: "type").toString();
279
280 debug() << subtitle;
281
282 if (type == "file") {
283 QString filename = subtitle.property(name: "name").toString();
284 if (!filename.isEmpty()) {
285 if (!m_player->setSubtitle(filename))
286 error() << "libVLC:" << LibVLC::errorMessage();
287 else
288 m_currentSubtitle = subtitle;
289
290 // There is no subtitle event inside libvlc so let's send our own event...
291 GlobalSubtitles::instance()->add(obj: this, descriptor: m_currentSubtitle);
292 emit availableSubtitlesChanged();
293 }
294 } else {
295 const int localIndex = GlobalSubtitles::instance()->localIdFor(obj: this, key: subtitle.index());
296 debug () << "localid" << localIndex;
297 if (!m_player->setSubtitle(localIndex))
298 error() << "libVLC:" << LibVLC::errorMessage();
299 else
300 m_currentSubtitle = subtitle;
301 }
302}
303
304void MediaController::setCurrentSubtitleFile(const QUrl &url)
305{
306 const QString file = url.toLocalFile();
307 if (!m_player->setSubtitle(file))
308 error() << "libVLC failed to set subtitle file:" << LibVLC::errorMessage();
309 // Unfortunately the addition of SPUs does not trigger an event in the
310 // VLC mediaplayer, yet the actual addition to the descriptor is async.
311 // So for the time being our best shot at getting an up-to-date list of SPUs
312 // is shooting in the dark and hoping we hit something.
313 // Refresha after 1, 2 and 5 seconds. If we have no updated list after 5
314 // seconds we are out of luck.
315 // https://trac.videolan.org/vlc/ticket/9796
316 QObject *mediaObject = dynamic_cast<QObject *>(this); // MediaObject : QObject, MediaController
317 m_refreshTimer->singleShot(msec: 1 * 1000, receiver: mediaObject, SLOT(refreshDescriptors()));
318 m_refreshTimer->singleShot(msec: 2 * 1000, receiver: mediaObject, SLOT(refreshDescriptors()));
319 m_refreshTimer->singleShot(msec: 5 * 1000, receiver: mediaObject, SLOT(refreshDescriptors()));
320}
321
322QList<Phonon::SubtitleDescription> MediaController::availableSubtitles() const
323{
324 return GlobalSubtitles::instance()->listFor(obj: this);
325}
326
327Phonon::SubtitleDescription MediaController::currentSubtitle() const
328{
329 return m_currentSubtitle;
330}
331
332void MediaController::refreshSubtitles()
333{
334 DEBUG_BLOCK;
335 GlobalSubtitles::instance()->clearListFor(obj: this);
336
337 const int currentSubtitleId = m_player->subtitle();
338
339 VLC_FOREACH_TRACK(it, m_player->videoSubtitleDescription()) {
340 debug() << "found subtitle" << it->psz_name << "[" << it->i_id << "]";
341 GlobalSubtitles::instance()->add(obj: this, index: it->i_id, name: QString::fromUtf8(utf8: it->psz_name), type: "");
342 if (it->i_id == currentSubtitleId) {
343#ifdef __GNUC__
344#warning GlobalDescriptionContainer does not allow reverse resolution from local to descriptor!
345#endif
346 const QList<SubtitleDescription> list = GlobalSubtitles::instance()->listFor(obj: this);
347 foreach (const SubtitleDescription &descriptor, list) {
348 if (descriptor.name() == QString::fromUtf8(utf8: it->psz_name)) {
349 m_currentSubtitle = descriptor;
350 }
351 }
352 }
353 }
354
355 emit availableSubtitlesChanged();
356}
357
358bool MediaController::subtitleAutodetect() const
359{
360 return m_subtitleAutodetect;
361}
362
363void MediaController::setSubtitleAutodetect(bool enabled)
364{
365 m_subtitleAutodetect = enabled;
366}
367
368QString MediaController::subtitleEncoding() const
369{
370 return m_subtitleEncoding;
371}
372
373void MediaController::setSubtitleEncoding(const QString &encoding)
374{
375 m_subtitleEncoding = encoding;
376}
377
378QFont MediaController::subtitleFont() const
379{
380 return m_subtitleFont;
381}
382
383void MediaController::setSubtitleFont(const QFont &font)
384{
385 m_subtitleFontChanged = true;
386 m_subtitleFont = font;
387}
388
389// --------------------------------- Title ---------------------------------- //
390void MediaController::setCurrentTitle(int title)
391{
392 DEBUG_BLOCK;
393 m_currentTitle = title;
394
395 switch (source().discType()) {
396 case Cd:
397 m_player->setCdTrack(title);
398 return;
399 case Dvd:
400 case Vcd:
401 case BluRay:
402 m_player->setTitle(title);
403 return;
404 case NoDisc:
405 warning() << "Current media source is not a CD, DVD or VCD!";
406 return;
407 }
408
409 warning() << "MediaSource does not support setting of tile in this version of Phonon VLC!"
410 << "Type is" << source().discType();
411}
412
413int MediaController::availableTitles() const
414{
415 return m_availableTitles;
416}
417
418int MediaController::currentTitle() const
419{
420 return m_currentTitle;
421}
422
423void MediaController::setAutoplayTitles(bool autoplay)
424{
425 m_autoPlayTitles = autoplay;
426}
427
428bool MediaController::autoplayTitles() const
429{
430 return m_autoPlayTitles;
431}
432
433void MediaController::refreshTitles()
434{
435 m_availableTitles = 0;
436
437 SharedTitleDescriptions list = m_player->titleDescription();
438 for (unsigned int i = 0; i < list->size(); ++i) {
439 ++m_availableTitles;
440 emit availableTitlesChanged(m_availableTitles);
441 }
442}
443
444// -------------------------------- Chapter --------------------------------- //
445void MediaController::setCurrentChapter(int chapter)
446{
447 m_currentChapter = chapter;
448 m_player->setChapter(chapter);
449}
450
451int MediaController::availableChapters() const
452{
453 return m_availableChapters;
454}
455
456int MediaController::currentChapter() const
457{
458 return m_currentChapter;
459}
460
461// We need to rebuild available chapters when title is changed
462void MediaController::refreshChapters(int title)
463{
464 m_availableChapters = 0;
465
466 // Get the description of available chapters for specific title
467 SharedChapterDescriptions list = m_player->videoChapterDescription(title);
468 for (unsigned int i = 0; i < list->size(); ++i) {
469 ++m_availableChapters;
470 emit availableChaptersChanged(m_availableChapters);
471 }
472}
473
474// --------------------------------- Angle ---------------------------------- //
475// NOT SUPPORTED IN LIBVLC //
476
477} // namespace VLC
478} // namespace Phonon
479

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