| 1 | /* |
| 2 | Copyright (C) 2011-2018 Harald Sitter <sitter@kde.org> |
| 3 | |
| 4 | This library is free software; you can redistribute it and/or |
| 5 | modify it under the terms of the GNU Lesser General Public |
| 6 | License as published by the Free Software Foundation; either |
| 7 | version 2.1 of the License, or (at your option) any later version. |
| 8 | |
| 9 | This library is distributed in the hope that it will be useful, |
| 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | Lesser General Public License for more details. |
| 13 | |
| 14 | You should have received a copy of the GNU Lesser General Public |
| 15 | License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| 16 | */ |
| 17 | |
| 18 | #include "mediaplayer.h" |
| 19 | |
| 20 | #include <QtCore/QDebug> |
| 21 | #include <QtCore/QDir> |
| 22 | #include <QtCore/QMetaType> |
| 23 | #include <QtCore/QString> |
| 24 | #include <QtCore/QTemporaryFile> |
| 25 | #include <QtGui/QImage> |
| 26 | |
| 27 | #include <vlc/libvlc_version.h> |
| 28 | |
| 29 | #include "utils/libvlc.h" |
| 30 | #include "media.h" |
| 31 | |
| 32 | // Callbacks come from a VLC thread. In some cases Qt fails to detect this and |
| 33 | // tries to invoke directly (i.e. from same thread). This can lead to thread |
| 34 | // pollution throughout Phonon, which is very much not desired. |
| 35 | #define P_EMIT_HAS_VIDEO(hasVideo) \ |
| 36 | QMetaObject::invokeMethod(\ |
| 37 | that, "hasVideoChanged", \ |
| 38 | Qt::QueuedConnection, \ |
| 39 | Q_ARG(bool, hasVideo)) |
| 40 | |
| 41 | #define P_EMIT_STATE(__state) \ |
| 42 | QMetaObject::invokeMethod(\ |
| 43 | that, "stateChanged", \ |
| 44 | Qt::QueuedConnection, \ |
| 45 | Q_ARG(MediaPlayer::State, __state)) |
| 46 | |
| 47 | namespace Phonon { |
| 48 | namespace VLC { |
| 49 | |
| 50 | MediaPlayer::MediaPlayer(QObject *parent) |
| 51 | : QObject(parent) |
| 52 | , m_media(0) |
| 53 | , m_player(libvlc_media_player_new(pvlc_libvlc)) |
| 54 | , m_doingPausedPlay(false) |
| 55 | , m_volume(75) |
| 56 | , m_fadeAmount(1.0f) |
| 57 | { |
| 58 | Q_ASSERT(m_player); |
| 59 | |
| 60 | qRegisterMetaType<MediaPlayer::State>(typeName: "MediaPlayer::State" ); |
| 61 | |
| 62 | libvlc_event_manager_t *manager = libvlc_media_player_event_manager(p_mi: m_player); |
| 63 | libvlc_event_type_t events[] = { |
| 64 | libvlc_MediaPlayerMediaChanged, |
| 65 | libvlc_MediaPlayerNothingSpecial, |
| 66 | libvlc_MediaPlayerOpening, |
| 67 | libvlc_MediaPlayerBuffering, |
| 68 | libvlc_MediaPlayerPlaying, |
| 69 | libvlc_MediaPlayerPaused, |
| 70 | libvlc_MediaPlayerStopped, |
| 71 | libvlc_MediaPlayerForward, |
| 72 | libvlc_MediaPlayerBackward, |
| 73 | libvlc_MediaPlayerEndReached, |
| 74 | libvlc_MediaPlayerEncounteredError, |
| 75 | libvlc_MediaPlayerTimeChanged, |
| 76 | libvlc_MediaPlayerPositionChanged, |
| 77 | libvlc_MediaPlayerSeekableChanged, |
| 78 | libvlc_MediaPlayerPausableChanged, |
| 79 | libvlc_MediaPlayerTitleChanged, |
| 80 | libvlc_MediaPlayerSnapshotTaken, |
| 81 | libvlc_MediaPlayerLengthChanged, |
| 82 | libvlc_MediaPlayerVout, |
| 83 | libvlc_MediaPlayerCorked, |
| 84 | libvlc_MediaPlayerUncorked, |
| 85 | libvlc_MediaPlayerMuted, |
| 86 | libvlc_MediaPlayerUnmuted, |
| 87 | libvlc_MediaPlayerAudioVolume |
| 88 | }; |
| 89 | const int eventCount = sizeof(events) / sizeof(*events); |
| 90 | for (int i = 0; i < eventCount; ++i) { |
| 91 | libvlc_event_attach(p_event_manager: manager, i_event_type: events[i], f_callback: event_cb, user_data: this); |
| 92 | } |
| 93 | |
| 94 | // Deactivate video title overlay (i.e. name of the video displaying |
| 95 | // at start. Since 2.1 that is handled via the API which in general is more |
| 96 | // reliable than setting it via libvlc_new (or so I have been told....) |
| 97 | libvlc_media_player_set_video_title_display(p_mi: m_player, position: libvlc_position_disable, timeout: 0); |
| 98 | } |
| 99 | |
| 100 | MediaPlayer::~MediaPlayer() |
| 101 | { |
| 102 | libvlc_media_player_release(p_mi: m_player); |
| 103 | } |
| 104 | |
| 105 | void MediaPlayer::setMedia(Media *media) |
| 106 | { |
| 107 | m_media = media; |
| 108 | libvlc_media_player_set_media(p_mi: m_player, p_md: *m_media); |
| 109 | } |
| 110 | |
| 111 | bool MediaPlayer::play() |
| 112 | { |
| 113 | m_doingPausedPlay = false; |
| 114 | return libvlc_media_player_play(p_mi: m_player) == 0; |
| 115 | } |
| 116 | |
| 117 | void MediaPlayer::pause() |
| 118 | { |
| 119 | m_doingPausedPlay = false; |
| 120 | libvlc_media_player_set_pause(mp: m_player, do_pause: 1); |
| 121 | } |
| 122 | |
| 123 | void MediaPlayer::pausedPlay() |
| 124 | { |
| 125 | m_doingPausedPlay = true; |
| 126 | libvlc_media_player_play(p_mi: m_player); |
| 127 | } |
| 128 | |
| 129 | void MediaPlayer::resume() |
| 130 | { |
| 131 | m_doingPausedPlay = false; |
| 132 | libvlc_media_player_set_pause(mp: m_player, do_pause: 0); |
| 133 | } |
| 134 | |
| 135 | void MediaPlayer::togglePause() |
| 136 | { |
| 137 | libvlc_media_player_pause(p_mi: m_player); |
| 138 | } |
| 139 | |
| 140 | void MediaPlayer::stop() |
| 141 | { |
| 142 | m_doingPausedPlay = false; |
| 143 | #ifdef __GNUC__ |
| 144 | #warning changed to stop_async does this have impliciations |
| 145 | #endif |
| 146 | #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0)) |
| 147 | libvlc_media_player_stop_async(m_player); |
| 148 | #else |
| 149 | libvlc_media_player_stop(p_mi: m_player); |
| 150 | #endif |
| 151 | } |
| 152 | |
| 153 | qint64 MediaPlayer::length() const |
| 154 | { |
| 155 | return libvlc_media_player_get_length(p_mi: m_player); |
| 156 | } |
| 157 | |
| 158 | qint64 MediaPlayer::time() const |
| 159 | { |
| 160 | return libvlc_media_player_get_time(p_mi: m_player); |
| 161 | } |
| 162 | |
| 163 | void MediaPlayer::setTime(qint64 newTime) |
| 164 | { |
| 165 | #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0)) |
| 166 | libvlc_media_player_set_time(m_player, newTime, false /* not fast, but precise */); |
| 167 | #else |
| 168 | libvlc_media_player_set_time(p_mi: m_player, i_time: newTime); |
| 169 | #endif |
| 170 | } |
| 171 | |
| 172 | bool MediaPlayer::isSeekable() const |
| 173 | { |
| 174 | return libvlc_media_player_is_seekable(p_mi: m_player); |
| 175 | } |
| 176 | |
| 177 | bool MediaPlayer::hasVideoOutput() const |
| 178 | { |
| 179 | return libvlc_media_player_has_vout(p_mi: m_player) > 0; |
| 180 | } |
| 181 | |
| 182 | bool MediaPlayer::setSubtitle(int subtitle) |
| 183 | { |
| 184 | return libvlc_video_set_spu(p_mi: m_player, i_spu: subtitle) == 0; |
| 185 | } |
| 186 | |
| 187 | bool MediaPlayer::setSubtitle(const QString &file) |
| 188 | { |
| 189 | return libvlc_media_player_add_slave(p_mi: m_player, |
| 190 | i_type: libvlc_media_slave_type_subtitle, |
| 191 | psz_uri: file.toUtf8().data(), |
| 192 | b_select: true) == 0; |
| 193 | } |
| 194 | |
| 195 | void MediaPlayer::setTitle(int title) |
| 196 | { |
| 197 | libvlc_media_player_set_title(p_mi: m_player, i_title: title); |
| 198 | } |
| 199 | |
| 200 | void MediaPlayer::setChapter(int chapter) |
| 201 | { |
| 202 | libvlc_media_player_set_chapter(p_mi: m_player, i_chapter: chapter); |
| 203 | } |
| 204 | |
| 205 | QImage MediaPlayer::snapshot() const |
| 206 | { |
| 207 | QTemporaryFile tempFile(QDir::tempPath() % QDir::separator() % QStringLiteral("phonon-vlc-snapshot" )); |
| 208 | tempFile.open(); |
| 209 | |
| 210 | // This function is sync. |
| 211 | if (libvlc_video_take_snapshot(p_mi: m_player, num: 0, psz_filepath: tempFile.fileName().toLocal8Bit().data(), i_width: 0, i_height: 0) != 0) |
| 212 | return QImage(); |
| 213 | |
| 214 | return QImage(tempFile.fileName()); |
| 215 | } |
| 216 | |
| 217 | bool MediaPlayer::setAudioTrack(int track) |
| 218 | { |
| 219 | return libvlc_audio_set_track(p_mi: m_player, i_track: track) == 0; |
| 220 | } |
| 221 | |
| 222 | void MediaPlayer::event_cb(const libvlc_event_t *event, void *opaque) |
| 223 | { |
| 224 | MediaPlayer *that = reinterpret_cast<MediaPlayer *>(opaque); |
| 225 | Q_ASSERT(that); |
| 226 | |
| 227 | // Do not forget to register for the events you want to handle here! |
| 228 | switch (event->type) { |
| 229 | case libvlc_MediaPlayerTimeChanged: |
| 230 | QMetaObject::invokeMethod( |
| 231 | obj: that, member: "timeChanged" , |
| 232 | c: Qt::QueuedConnection, |
| 233 | Q_ARG(qint64, event->u.media_player_time_changed.new_time)); |
| 234 | break; |
| 235 | case libvlc_MediaPlayerSeekableChanged: |
| 236 | QMetaObject::invokeMethod( |
| 237 | obj: that, member: "seekableChanged" , |
| 238 | c: Qt::QueuedConnection, |
| 239 | Q_ARG(bool, event->u.media_player_seekable_changed.new_seekable)); |
| 240 | break; |
| 241 | case libvlc_MediaPlayerLengthChanged: |
| 242 | QMetaObject::invokeMethod( |
| 243 | obj: that, member: "lengthChanged" , |
| 244 | c: Qt::QueuedConnection, |
| 245 | Q_ARG(qint64, event->u.media_player_length_changed.new_length)); |
| 246 | break; |
| 247 | case libvlc_MediaPlayerNothingSpecial: |
| 248 | P_EMIT_STATE(NoState); |
| 249 | break; |
| 250 | case libvlc_MediaPlayerOpening: |
| 251 | P_EMIT_STATE(OpeningState); |
| 252 | break; |
| 253 | case libvlc_MediaPlayerBuffering: |
| 254 | QMetaObject::invokeMethod( |
| 255 | obj: that, member: "bufferChanged" , |
| 256 | c: Qt::QueuedConnection, |
| 257 | Q_ARG(int, event->u.media_player_buffering.new_cache)); |
| 258 | break; |
| 259 | case libvlc_MediaPlayerPlaying: |
| 260 | // Intercept state change and apply pausing once playing. |
| 261 | if (that->m_doingPausedPlay) { |
| 262 | that->m_doingPausedPlay = false; |
| 263 | // VLC internally will call stop if a player can not be paused, this |
| 264 | // can lead to deadlocks as stop is partially blocking, to avoid this |
| 265 | // we explicitly do a queued stop whenever a player can not be paused. |
| 266 | // In those situations pausedplay capability can not be provided, so |
| 267 | // applications wanting to do pausedplay will need to handle it anyway |
| 268 | // as faking a paused state when there is none would be a very code |
| 269 | // intense workaround asking for weird abstraction leakage. |
| 270 | // See kde bug 337604. |
| 271 | if (libvlc_media_player_can_pause(p_mi: that->m_player)) { |
| 272 | that->pause(); |
| 273 | } else { |
| 274 | QMetaObject::invokeMethod(obj: that, member: "stop" , c: Qt::QueuedConnection); |
| 275 | } |
| 276 | } else |
| 277 | P_EMIT_STATE(PlayingState); |
| 278 | break; |
| 279 | case libvlc_MediaPlayerPaused: |
| 280 | P_EMIT_STATE(PausedState); |
| 281 | break; |
| 282 | case libvlc_MediaPlayerStopped: |
| 283 | P_EMIT_STATE(StoppedState); |
| 284 | break; |
| 285 | case libvlc_MediaPlayerEndReached: |
| 286 | P_EMIT_STATE(EndedState); |
| 287 | break; |
| 288 | case libvlc_MediaPlayerEncounteredError: |
| 289 | P_EMIT_STATE(ErrorState); |
| 290 | break; |
| 291 | case libvlc_MediaPlayerVout: |
| 292 | if (event->u.media_player_vout.new_count > 0) { |
| 293 | P_EMIT_HAS_VIDEO(true); |
| 294 | } else { |
| 295 | P_EMIT_HAS_VIDEO(false); |
| 296 | } |
| 297 | break; |
| 298 | case libvlc_MediaPlayerMediaChanged: |
| 299 | break; |
| 300 | case libvlc_MediaPlayerCorked: |
| 301 | that->pause(); |
| 302 | break; |
| 303 | case libvlc_MediaPlayerUncorked: |
| 304 | that->play(); |
| 305 | break; |
| 306 | case libvlc_MediaPlayerMuted: |
| 307 | QMetaObject::invokeMethod( |
| 308 | obj: that, member: "mutedChanged" , |
| 309 | c: Qt::QueuedConnection, |
| 310 | Q_ARG(bool, true)); |
| 311 | break; |
| 312 | case libvlc_MediaPlayerUnmuted: |
| 313 | QMetaObject::invokeMethod( |
| 314 | obj: that, member: "mutedChanged" , |
| 315 | c: Qt::QueuedConnection, |
| 316 | Q_ARG(bool, false)); |
| 317 | break; |
| 318 | case libvlc_MediaPlayerAudioVolume: |
| 319 | QMetaObject::invokeMethod( |
| 320 | obj: that, member: "volumeChanged" , |
| 321 | c: Qt::QueuedConnection, |
| 322 | Q_ARG(float, event->u.media_player_audio_volume.volume)); |
| 323 | break; |
| 324 | case libvlc_MediaPlayerForward: |
| 325 | case libvlc_MediaPlayerBackward: |
| 326 | case libvlc_MediaPlayerPositionChanged: |
| 327 | case libvlc_MediaPlayerPausableChanged: |
| 328 | case libvlc_MediaPlayerTitleChanged: |
| 329 | case libvlc_MediaPlayerSnapshotTaken: // Snapshot call is sync, so this is useless. |
| 330 | default: |
| 331 | break; |
| 332 | QString msg = QString("Unknown event: " ) + QString::number(event->type); |
| 333 | Q_ASSERT_X(false, "event_cb" , qPrintable(msg)); |
| 334 | break; |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | QDebug operator<<(QDebug dbg, const MediaPlayer::State &s) |
| 339 | { |
| 340 | QString name; |
| 341 | switch (s) { |
| 342 | case MediaPlayer::NoState: |
| 343 | name = QLatin1String("MediaPlayer::NoState" ); |
| 344 | break; |
| 345 | case MediaPlayer::OpeningState: |
| 346 | name = QLatin1String("MediaPlayer::OpeningState" ); |
| 347 | break; |
| 348 | case MediaPlayer::BufferingState: |
| 349 | name = QLatin1String("MediaPlayer::BufferingState" ); |
| 350 | break; |
| 351 | case MediaPlayer::PlayingState: |
| 352 | name = QLatin1String("MediaPlayer::PlayingState" ); |
| 353 | break; |
| 354 | case MediaPlayer::PausedState: |
| 355 | name = QLatin1String("MediaPlayer::PausedState" ); |
| 356 | break; |
| 357 | case MediaPlayer::StoppedState: |
| 358 | name = QLatin1String("MediaPlayer::StoppedState" ); |
| 359 | break; |
| 360 | case MediaPlayer::EndedState: |
| 361 | name = QLatin1String("MediaPlayer::EndedState" ); |
| 362 | break; |
| 363 | case MediaPlayer::ErrorState: |
| 364 | name = QLatin1String("MediaPlayer::ErrorState" ); |
| 365 | break; |
| 366 | } |
| 367 | dbg.nospace() << "State(" << qPrintable(name) << ")" ; |
| 368 | return dbg.space(); |
| 369 | } |
| 370 | |
| 371 | void MediaPlayer::setAudioFade(qreal fade) |
| 372 | { |
| 373 | m_fadeAmount = fade; |
| 374 | setVolumeInternal(); |
| 375 | } |
| 376 | |
| 377 | void MediaPlayer::setAudioVolume(int volume) |
| 378 | { |
| 379 | m_volume = volume; |
| 380 | setVolumeInternal(); |
| 381 | } |
| 382 | |
| 383 | bool MediaPlayer::mute() const |
| 384 | { |
| 385 | return libvlc_audio_get_mute(p_mi: m_player); |
| 386 | } |
| 387 | |
| 388 | void MediaPlayer::setMute(bool mute) |
| 389 | { |
| 390 | libvlc_audio_set_mute(p_mi: m_player, status: mute); |
| 391 | } |
| 392 | |
| 393 | void MediaPlayer::setVolumeInternal() |
| 394 | { |
| 395 | libvlc_audio_set_volume(p_mi: m_player, i_volume: m_volume * m_fadeAmount); |
| 396 | } |
| 397 | |
| 398 | void MediaPlayer::setCdTrack(int track) |
| 399 | { |
| 400 | if (!m_media) |
| 401 | return; |
| 402 | #ifdef __GNUC__ |
| 403 | #warning changed to stop_async does this have impliciations |
| 404 | #endif |
| 405 | #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0)) |
| 406 | libvlc_media_player_stop_async(m_player); |
| 407 | #else |
| 408 | libvlc_media_player_stop(p_mi: m_player); |
| 409 | #endif |
| 410 | m_media->setCdTrack(track); |
| 411 | libvlc_media_player_set_media(p_mi: m_player, p_md: *m_media); |
| 412 | libvlc_media_player_play(p_mi: m_player); |
| 413 | } |
| 414 | |
| 415 | void MediaPlayer::setEqualizer(libvlc_equalizer_t *equalizer) |
| 416 | { |
| 417 | libvlc_media_player_set_equalizer(p_mi: m_player, p_equalizer: equalizer); |
| 418 | } |
| 419 | |
| 420 | } // namespace VLC |
| 421 | } // namespace Phonon |
| 422 | |