File indexing completed on 2024-12-01 04:21:26

0001 /*
0002     Copyright (C) 2011-2018 Harald Sitter <sitter@kde.org>
0003 
0004     This library is free software; you can redistribute it and/or
0005     modify it under the terms of the GNU Lesser General Public
0006     License as published by the Free Software Foundation; either
0007     version 2.1 of the License, or (at your option) any later version.
0008 
0009     This library is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012     Lesser General Public License for more details.
0013 
0014     You should have received a copy of the GNU Lesser General Public
0015     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0016 */
0017 
0018 #include "mediaplayer.h"
0019 
0020 #include <QtCore/QDebug>
0021 #include <QtCore/QDir>
0022 #include <QtCore/QMetaType>
0023 #include <QtCore/QString>
0024 #include <QtCore/QTemporaryFile>
0025 #include <QtGui/QImage>
0026 
0027 #include <vlc/libvlc_version.h>
0028 
0029 #include "utils/libvlc.h"
0030 #include "media.h"
0031 
0032 // Callbacks come from a VLC thread. In some cases Qt fails to detect this and
0033 // tries to invoke directly (i.e. from same thread). This can lead to thread
0034 // pollution throughout Phonon, which is very much not desired.
0035 #define P_EMIT_HAS_VIDEO(hasVideo) \
0036     QMetaObject::invokeMethod(\
0037         that, "hasVideoChanged", \
0038         Qt::QueuedConnection, \
0039         Q_ARG(bool, hasVideo))
0040 
0041 #define P_EMIT_STATE(__state) \
0042     QMetaObject::invokeMethod(\
0043         that, "stateChanged", \
0044         Qt::QueuedConnection, \
0045         Q_ARG(MediaPlayer::State, __state))
0046 
0047 namespace Phonon {
0048 namespace VLC {
0049 
0050 MediaPlayer::MediaPlayer(QObject *parent)
0051     : QObject(parent)
0052     , m_media(0)
0053     , m_player(libvlc_media_player_new(pvlc_libvlc))
0054     , m_doingPausedPlay(false)
0055     , m_volume(75)
0056     , m_fadeAmount(1.0f)
0057 {
0058     Q_ASSERT(m_player);
0059 
0060     qRegisterMetaType<MediaPlayer::State>("MediaPlayer::State");
0061 
0062     libvlc_event_manager_t *manager = libvlc_media_player_event_manager(m_player);
0063     libvlc_event_type_t events[] = {
0064         libvlc_MediaPlayerMediaChanged,
0065         libvlc_MediaPlayerNothingSpecial,
0066         libvlc_MediaPlayerOpening,
0067         libvlc_MediaPlayerBuffering,
0068         libvlc_MediaPlayerPlaying,
0069         libvlc_MediaPlayerPaused,
0070         libvlc_MediaPlayerStopped,
0071         libvlc_MediaPlayerForward,
0072         libvlc_MediaPlayerBackward,
0073         libvlc_MediaPlayerEndReached,
0074         libvlc_MediaPlayerEncounteredError,
0075         libvlc_MediaPlayerTimeChanged,
0076         libvlc_MediaPlayerPositionChanged,
0077         libvlc_MediaPlayerSeekableChanged,
0078         libvlc_MediaPlayerPausableChanged,
0079         libvlc_MediaPlayerTitleChanged,
0080         libvlc_MediaPlayerSnapshotTaken,
0081         libvlc_MediaPlayerLengthChanged,
0082         libvlc_MediaPlayerVout,
0083         libvlc_MediaPlayerCorked,
0084         libvlc_MediaPlayerUncorked,
0085         libvlc_MediaPlayerMuted,
0086         libvlc_MediaPlayerUnmuted,
0087         libvlc_MediaPlayerAudioVolume
0088     };
0089     const int eventCount = sizeof(events) / sizeof(*events);
0090     for (int i = 0; i < eventCount; ++i) {
0091         libvlc_event_attach(manager, events[i], event_cb, this);
0092     }
0093 
0094     // Deactivate video title overlay (i.e. name of the video displaying
0095     // at start. Since 2.1 that is handled via the API which in general is more
0096     // reliable than setting it via libvlc_new (or so I have been told....)
0097     libvlc_media_player_set_video_title_display(m_player, libvlc_position_disable, 0);
0098 }
0099 
0100 MediaPlayer::~MediaPlayer()
0101 {
0102     libvlc_media_player_release(m_player);
0103 }
0104 
0105 void MediaPlayer::setMedia(Media *media)
0106 {
0107     m_media = media;
0108     libvlc_media_player_set_media(m_player, *m_media);
0109 }
0110 
0111 bool MediaPlayer::play()
0112 {
0113     m_doingPausedPlay = false;
0114     return libvlc_media_player_play(m_player) == 0;
0115 }
0116 
0117 void MediaPlayer::pause()
0118 {
0119     m_doingPausedPlay = false;
0120     libvlc_media_player_set_pause(m_player, 1);
0121 }
0122 
0123 void MediaPlayer::pausedPlay()
0124 {
0125     m_doingPausedPlay = true;
0126     libvlc_media_player_play(m_player);
0127 }
0128 
0129 void MediaPlayer::resume()
0130 {
0131     m_doingPausedPlay = false;
0132     libvlc_media_player_set_pause(m_player, 0);
0133 }
0134 
0135 void MediaPlayer::togglePause()
0136 {
0137     libvlc_media_player_pause(m_player);
0138 }
0139 
0140 void MediaPlayer::stop()
0141 {
0142     m_doingPausedPlay = false;
0143 #ifdef __GNUC__
0144 #warning changed to stop_async does this have impliciations
0145 #endif
0146 #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0))
0147     libvlc_media_player_stop_async(m_player);
0148 #else
0149     libvlc_media_player_stop(m_player);
0150 #endif
0151 }
0152 
0153 qint64 MediaPlayer::length() const
0154 {
0155     return libvlc_media_player_get_length(m_player);
0156 }
0157 
0158 qint64 MediaPlayer::time() const
0159 {
0160     return libvlc_media_player_get_time(m_player);
0161 }
0162 
0163 void MediaPlayer::setTime(qint64 newTime)
0164 {
0165 #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0))
0166     libvlc_media_player_set_time(m_player, newTime, false /* not fast, but precise */);
0167 #else
0168     libvlc_media_player_set_time(m_player, newTime);
0169 #endif
0170 }
0171 
0172 bool MediaPlayer::isSeekable() const
0173 {
0174     return libvlc_media_player_is_seekable(m_player);
0175 }
0176 
0177 bool MediaPlayer::hasVideoOutput() const
0178 {
0179     return libvlc_media_player_has_vout(m_player) > 0;
0180 }
0181 
0182 bool MediaPlayer::setSubtitle(int subtitle)
0183 {
0184     return libvlc_video_set_spu(m_player, subtitle) == 0;
0185 }
0186 
0187 bool MediaPlayer::setSubtitle(const QString &file)
0188 {
0189     return libvlc_media_player_add_slave(m_player,
0190                                          libvlc_media_slave_type_subtitle,
0191                                          file.toUtf8().data(),
0192                                          true) == 0;
0193 }
0194 
0195 void MediaPlayer::setTitle(int title)
0196 {
0197     libvlc_media_player_set_title(m_player, title);
0198 }
0199 
0200 void MediaPlayer::setChapter(int chapter)
0201 {
0202     libvlc_media_player_set_chapter(m_player, chapter);
0203 }
0204 
0205 QImage MediaPlayer::snapshot() const
0206 {
0207     QTemporaryFile tempFile(QDir::tempPath() % QDir::separator() % QStringLiteral("phonon-vlc-snapshot"));
0208     tempFile.open();
0209 
0210     // This function is sync.
0211     if (libvlc_video_take_snapshot(m_player, 0, tempFile.fileName().toLocal8Bit().data(), 0, 0) != 0)
0212         return QImage();
0213 
0214     return QImage(tempFile.fileName());
0215 }
0216 
0217 bool MediaPlayer::setAudioTrack(int track)
0218 {
0219     return libvlc_audio_set_track(m_player, track) == 0;
0220 }
0221 
0222 void MediaPlayer::event_cb(const libvlc_event_t *event, void *opaque)
0223 {
0224     MediaPlayer *that = reinterpret_cast<MediaPlayer *>(opaque);
0225     Q_ASSERT(that);
0226 
0227     // Do not forget to register for the events you want to handle here!
0228     switch (event->type) {
0229     case libvlc_MediaPlayerTimeChanged:
0230         QMetaObject::invokeMethod(
0231                     that, "timeChanged",
0232                     Qt::QueuedConnection,
0233                     Q_ARG(qint64, event->u.media_player_time_changed.new_time));
0234         break;
0235     case libvlc_MediaPlayerSeekableChanged:
0236         QMetaObject::invokeMethod(
0237                     that, "seekableChanged",
0238                     Qt::QueuedConnection,
0239                     Q_ARG(bool, event->u.media_player_seekable_changed.new_seekable));
0240         break;
0241     case libvlc_MediaPlayerLengthChanged:
0242         QMetaObject::invokeMethod(
0243                     that, "lengthChanged",
0244                     Qt::QueuedConnection,
0245                     Q_ARG(qint64, event->u.media_player_length_changed.new_length));
0246         break;
0247     case libvlc_MediaPlayerNothingSpecial:
0248         P_EMIT_STATE(NoState);
0249         break;
0250     case libvlc_MediaPlayerOpening:
0251         P_EMIT_STATE(OpeningState);
0252         break;
0253     case libvlc_MediaPlayerBuffering:
0254         QMetaObject::invokeMethod(
0255                     that, "bufferChanged",
0256                     Qt::QueuedConnection,
0257                     Q_ARG(int, event->u.media_player_buffering.new_cache));
0258         break;
0259     case libvlc_MediaPlayerPlaying:
0260         // Intercept state change and apply pausing once playing.
0261         if (that->m_doingPausedPlay) {
0262             that->m_doingPausedPlay = false;
0263             // VLC internally will call stop if a player can not be paused, this
0264             // can lead to deadlocks as stop is partially blocking, to avoid this
0265             // we explicitly do a queued stop whenever a player can not be paused.
0266             // In those situations pausedplay capability can not be provided, so
0267             // applications wanting to do pausedplay will need to handle it anyway
0268             // as faking a paused state when there is none would be a very code
0269             // intense workaround asking for weird abstraction leakage.
0270             // See kde bug 337604.
0271             if (libvlc_media_player_can_pause(that->m_player)) {
0272                 that->pause();
0273             } else {
0274                 QMetaObject::invokeMethod(that, "stop", Qt::QueuedConnection);
0275             }
0276         } else
0277             P_EMIT_STATE(PlayingState);
0278         break;
0279     case libvlc_MediaPlayerPaused:
0280         P_EMIT_STATE(PausedState);
0281         break;
0282     case libvlc_MediaPlayerStopped:
0283         P_EMIT_STATE(StoppedState);
0284         break;
0285     case libvlc_MediaPlayerEndReached:
0286         P_EMIT_STATE(EndedState);
0287         break;
0288     case libvlc_MediaPlayerEncounteredError:
0289         P_EMIT_STATE(ErrorState);
0290         break;
0291     case libvlc_MediaPlayerVout:
0292         if (event->u.media_player_vout.new_count > 0) {
0293             P_EMIT_HAS_VIDEO(true);
0294         } else {
0295             P_EMIT_HAS_VIDEO(false);
0296         }
0297         break;
0298     case libvlc_MediaPlayerMediaChanged:
0299         break;
0300     case libvlc_MediaPlayerCorked:
0301         that->pause();
0302         break;
0303     case libvlc_MediaPlayerUncorked:
0304         that->play();
0305         break;
0306     case libvlc_MediaPlayerMuted:
0307         QMetaObject::invokeMethod(
0308                     that, "mutedChanged",
0309                     Qt::QueuedConnection,
0310                     Q_ARG(bool, true));
0311         break;
0312     case libvlc_MediaPlayerUnmuted:
0313         QMetaObject::invokeMethod(
0314                     that, "mutedChanged",
0315                     Qt::QueuedConnection,
0316                     Q_ARG(bool, false));
0317         break;
0318     case libvlc_MediaPlayerAudioVolume:
0319         QMetaObject::invokeMethod(
0320                     that, "volumeChanged",
0321                     Qt::QueuedConnection,
0322                     Q_ARG(float, event->u.media_player_audio_volume.volume));
0323         break;
0324     case libvlc_MediaPlayerForward:
0325     case libvlc_MediaPlayerBackward:
0326     case libvlc_MediaPlayerPositionChanged:
0327     case libvlc_MediaPlayerPausableChanged:
0328     case libvlc_MediaPlayerTitleChanged:
0329     case libvlc_MediaPlayerSnapshotTaken: // Snapshot call is sync, so this is useless.
0330     default:
0331         break;
0332         QString msg = QString("Unknown event: ") + QString::number(event->type);
0333         Q_ASSERT_X(false, "event_cb", qPrintable(msg));
0334         break;
0335     }
0336 }
0337 
0338 QDebug operator<<(QDebug dbg, const MediaPlayer::State &s)
0339 {
0340     QString name;
0341     switch (s) {
0342     case MediaPlayer::NoState:
0343         name = QLatin1String("MediaPlayer::NoState");
0344         break;
0345     case MediaPlayer::OpeningState:
0346         name = QLatin1String("MediaPlayer::OpeningState");
0347         break;
0348     case MediaPlayer::BufferingState:
0349         name = QLatin1String("MediaPlayer::BufferingState");
0350         break;
0351     case MediaPlayer::PlayingState:
0352         name = QLatin1String("MediaPlayer::PlayingState");
0353         break;
0354     case MediaPlayer::PausedState:
0355         name = QLatin1String("MediaPlayer::PausedState");
0356         break;
0357     case MediaPlayer::StoppedState:
0358         name = QLatin1String("MediaPlayer::StoppedState");
0359         break;
0360     case MediaPlayer::EndedState:
0361         name = QLatin1String("MediaPlayer::EndedState");
0362         break;
0363     case MediaPlayer::ErrorState:
0364         name = QLatin1String("MediaPlayer::ErrorState");
0365         break;
0366     }
0367     dbg.nospace() << "State(" << qPrintable(name) << ")";
0368     return dbg.space();
0369 }
0370 
0371 void MediaPlayer::setAudioFade(qreal fade)
0372 {
0373     m_fadeAmount = fade;
0374     setVolumeInternal();
0375 }
0376 
0377 void MediaPlayer::setAudioVolume(int volume)
0378 {
0379     m_volume = volume;
0380     setVolumeInternal();
0381 }
0382 
0383 bool MediaPlayer::mute() const
0384 {
0385     return libvlc_audio_get_mute(m_player);
0386 }
0387 
0388 void MediaPlayer::setMute(bool mute)
0389 {
0390     libvlc_audio_set_mute(m_player, mute);
0391 }
0392 
0393 void MediaPlayer::setVolumeInternal()
0394 {
0395     libvlc_audio_set_volume(m_player, m_volume * m_fadeAmount);
0396 }
0397 
0398 void MediaPlayer::setCdTrack(int track)
0399 {
0400     if (!m_media)
0401         return;
0402 #ifdef __GNUC__
0403 #warning changed to stop_async does this have impliciations
0404 #endif
0405 #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0))
0406     libvlc_media_player_stop_async(m_player);
0407 #else
0408     libvlc_media_player_stop(m_player);
0409 #endif
0410     m_media->setCdTrack(track);
0411     libvlc_media_player_set_media(m_player, *m_media);
0412     libvlc_media_player_play(m_player);
0413 }
0414 
0415 void MediaPlayer::setEqualizer(libvlc_equalizer_t *equalizer)
0416 {
0417     libvlc_media_player_set_equalizer(m_player, equalizer);
0418 }
0419 
0420 } // namespace VLC
0421 } // namespace Phonon