| 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 | |
| 32 | namespace Phonon { |
| 33 | namespace VLC { |
| 34 | |
| 35 | #ifdef __GNUC__ |
| 36 | #warning titles and chapters not covered by globaldescriptioncontainer!! |
| 37 | #endif |
| 38 | |
| 39 | MediaController::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 | |
| 52 | MediaController::~MediaController() |
| 53 | { |
| 54 | GlobalSubtitles::instance()->unregister_(obj: this); |
| 55 | GlobalAudioChannels::instance()->unregister_(obj: this); |
| 56 | } |
| 57 | |
| 58 | bool 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 | |
| 85 | QVariant 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 | |
| 201 | void MediaController::resetMediaController() |
| 202 | { |
| 203 | resetMembers(); |
| 204 | emit availableAudioChannelsChanged(); |
| 205 | emit availableSubtitlesChanged(); |
| 206 | emit availableTitlesChanged(0); |
| 207 | emit availableChaptersChanged(0); |
| 208 | } |
| 209 | |
| 210 | void 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 ------------------------------ // |
| 228 | void 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 | |
| 237 | QList<Phonon::AudioChannelDescription> MediaController::availableAudioChannels() const |
| 238 | { |
| 239 | return GlobalAudioChannels::instance()->listFor(obj: this); |
| 240 | } |
| 241 | |
| 242 | Phonon::AudioChannelDescription MediaController::currentAudioChannel() const |
| 243 | { |
| 244 | return m_currentAudioChannel; |
| 245 | } |
| 246 | |
| 247 | void 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 -------------------------------- // |
| 275 | void 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 | |
| 304 | void 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 | |
| 322 | QList<Phonon::SubtitleDescription> MediaController::availableSubtitles() const |
| 323 | { |
| 324 | return GlobalSubtitles::instance()->listFor(obj: this); |
| 325 | } |
| 326 | |
| 327 | Phonon::SubtitleDescription MediaController::currentSubtitle() const |
| 328 | { |
| 329 | return m_currentSubtitle; |
| 330 | } |
| 331 | |
| 332 | void 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 | |
| 358 | bool MediaController::subtitleAutodetect() const |
| 359 | { |
| 360 | return m_subtitleAutodetect; |
| 361 | } |
| 362 | |
| 363 | void MediaController::setSubtitleAutodetect(bool enabled) |
| 364 | { |
| 365 | m_subtitleAutodetect = enabled; |
| 366 | } |
| 367 | |
| 368 | QString MediaController::subtitleEncoding() const |
| 369 | { |
| 370 | return m_subtitleEncoding; |
| 371 | } |
| 372 | |
| 373 | void MediaController::setSubtitleEncoding(const QString &encoding) |
| 374 | { |
| 375 | m_subtitleEncoding = encoding; |
| 376 | } |
| 377 | |
| 378 | QFont MediaController::subtitleFont() const |
| 379 | { |
| 380 | return m_subtitleFont; |
| 381 | } |
| 382 | |
| 383 | void MediaController::setSubtitleFont(const QFont &font) |
| 384 | { |
| 385 | m_subtitleFontChanged = true; |
| 386 | m_subtitleFont = font; |
| 387 | } |
| 388 | |
| 389 | // --------------------------------- Title ---------------------------------- // |
| 390 | void 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 | |
| 413 | int MediaController::availableTitles() const |
| 414 | { |
| 415 | return m_availableTitles; |
| 416 | } |
| 417 | |
| 418 | int MediaController::currentTitle() const |
| 419 | { |
| 420 | return m_currentTitle; |
| 421 | } |
| 422 | |
| 423 | void MediaController::setAutoplayTitles(bool autoplay) |
| 424 | { |
| 425 | m_autoPlayTitles = autoplay; |
| 426 | } |
| 427 | |
| 428 | bool MediaController::autoplayTitles() const |
| 429 | { |
| 430 | return m_autoPlayTitles; |
| 431 | } |
| 432 | |
| 433 | void 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 --------------------------------- // |
| 445 | void MediaController::setCurrentChapter(int chapter) |
| 446 | { |
| 447 | m_currentChapter = chapter; |
| 448 | m_player->setChapter(chapter); |
| 449 | } |
| 450 | |
| 451 | int MediaController::availableChapters() const |
| 452 | { |
| 453 | return m_availableChapters; |
| 454 | } |
| 455 | |
| 456 | int MediaController::currentChapter() const |
| 457 | { |
| 458 | return m_currentChapter; |
| 459 | } |
| 460 | |
| 461 | // We need to rebuild available chapters when title is changed |
| 462 | void 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 | |