diff --git a/src/backend-vlc/vlcmediawidget.cpp b/src/backend-vlc/vlcmediawidget.cpp index 97a8e9c..afbcceb 100644 --- a/src/backend-vlc/vlcmediawidget.cpp +++ b/src/backend-vlc/vlcmediawidget.cpp @@ -1,853 +1,853 @@ /* * vlcmediawidget.cpp * * Copyright (C) 2010-2012 Christoph Pfister * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include "../configuration.h" #include "vlcmediawidget.h" VlcMediaWidget::VlcMediaWidget(QWidget *parent) : AbstractMediaWidget(parent), timer(NULL), vlcInstance(NULL), vlcMedia(NULL), vlcMediaPlayer(NULL), isPaused(false), playingDvd(false), urlIsAudioCd(false), typeOfDevice(""), trackNumber(1), numTracks(1) { } bool VlcMediaWidget::init() { QString args = Configuration::instance()->getLibVlcArguments(); QStringList argList; int argc = 0, size; argList = args.split(' ', QString::SkipEmptyParts); size = argList.size(); const char **argv = new const char *[size]; QVector str(size); for (int i = 0; i < size; i++) { str[i] = argList.at(i).toUtf8(); argv[argc++] = str[i]; } vlcInstance = libvlc_new(argc, argv); if (!vlcInstance) { qCWarning(logMediaWidget, "libVLC: failed to use extra args: %s", qPrintable(args)); argc = 0; vlcInstance = libvlc_new(0, NULL); if (vlcInstance) qCInfo(logMediaWidget, "Using libVLC without arguments"); } if (vlcInstance == NULL) { qFatal("Cannot create vlc instance %s", qPrintable(libvlc_errmsg())); delete[] argv; return false; } if (argc) { QString log = "Using libVLC with args:"; for (int i = 0; i < argc; i++) log += ' ' + QLatin1String(argv[i]); qCDebug(logVlc, "%s", qPrintable(log)); } delete[] argv; vlcMediaPlayer = libvlc_media_player_new(vlcInstance); if (vlcMediaPlayer == NULL) { qFatal("Cannot create vlc media player %s", qPrintable(libvlc_errmsg())); return false; } libvlc_event_manager_t *eventManager = libvlc_media_player_event_manager(vlcMediaPlayer); libvlc_event_e eventTypes[] = { libvlc_MediaPlayerEncounteredError, libvlc_MediaPlayerEndReached, libvlc_MediaPlayerLengthChanged, libvlc_MediaPlayerSeekableChanged, libvlc_MediaPlayerStopped, #if LIBVLC_VERSION_MAJOR > 2 libvlc_MediaPlayerESAdded, libvlc_MediaPlayerESDeleted, #endif libvlc_MediaPlayerTimeChanged }; for (uint i = 0; i < (sizeof(eventTypes) / sizeof(eventTypes[0])); ++i) { if (libvlc_event_attach(eventManager, eventTypes[i], vlcEventHandler, this) != 0) { qCCritical(logMediaWidget, "Cannot attach event handler %d", eventTypes[i]); return false; } } timer = new QTimer(); connect(timer, SIGNAL(timeout()), this, SLOT(hideMouse())); libvlc_media_player_set_xwindow(vlcMediaPlayer, quint32(winId())); setAttribute(Qt::WA_NativeWindow); libvlc_audio_set_mute(vlcMediaPlayer, false); // This is broken on qt5: the kernel/qwidget.cpp tries to repaint // on a wrong place, causing this warning: // QWidget::paintEngine: Should no longer be called // setAttribute(Qt::WA_PaintOnScreen); return true; } VlcMediaWidget::~VlcMediaWidget() { metadata.clear(); audioStreams.clear(); subtitles.clear(); subtitleId.clear(); if (timer != NULL) { timer->stop(); delete timer; } if (vlcMediaPlayer != NULL) { libvlc_media_player_release(vlcMediaPlayer); } if (vlcInstance != NULL) { libvlc_release(vlcInstance); } } VlcMediaWidget *VlcMediaWidget::createVlcMediaWidget(QWidget *parent) { QScopedPointer vlcMediaWidget(new VlcMediaWidget(parent)); if (!vlcMediaWidget->init()) { return NULL; } return vlcMediaWidget.take(); } QStringList VlcMediaWidget::getAudioDevices() { libvlc_audio_output_device_t *vlcAudioOutput, *i; QStringList audioDevices; // Get audio device list vlcAudioOutput = libvlc_audio_output_device_enum(vlcMediaPlayer); if (!vlcAudioOutput) return audioDevices; for (i = vlcAudioOutput; i != NULL; i = i->p_next) { QString device = QString::fromUtf8(i->psz_description); audioDevices.append(device); } libvlc_audio_output_device_list_release(vlcAudioOutput); return audioDevices; } void VlcMediaWidget::setAudioDevice(QString device) { libvlc_audio_output_device_t *vlcAudioOutput, *i; vlcAudioOutput = libvlc_audio_output_device_enum(vlcMediaPlayer); if (!vlcAudioOutput) return; for (i = vlcAudioOutput; i != NULL; i = i->p_next) { if (device.compare(QString::fromUtf8(i->psz_description))) continue; qCDebug(logVlc, "Setting audio output to: %s", qPrintable(i->psz_device)); libvlc_audio_output_device_set(vlcMediaPlayer, NULL, i->psz_device); } libvlc_audio_output_device_list_release(vlcAudioOutput); } void VlcMediaWidget::setMuted(bool muted) { libvlc_audio_set_mute(vlcMediaPlayer, muted); } void VlcMediaWidget::setVolume(int volume) { // 0 <= volume <= 200 if (libvlc_audio_set_volume(vlcMediaPlayer, volume) != 0) { qCWarning(logMediaWidget, "cannot set volume %i", volume); } } void VlcMediaWidget::setAspectRatio(MediaWidget::AspectRatio aspectRatio) { const char *vlcAspectRatio = ""; switch (aspectRatio) { case MediaWidget::AspectRatioAuto: break; case MediaWidget::AspectRatio1_1: vlcAspectRatio = "1:1"; break; case MediaWidget::AspectRatio4_3: vlcAspectRatio = "4:3"; break; case MediaWidget::AspectRatio5_4: vlcAspectRatio = "5:4"; break; case MediaWidget::AspectRatio16_9: vlcAspectRatio = "16:9"; break; case MediaWidget::AspectRatio16_10: vlcAspectRatio = "16:10"; break; case MediaWidget::AspectRatio221_100: vlcAspectRatio = "221:100"; break; case MediaWidget::AspectRatio235_100: vlcAspectRatio = "235:100"; break; case MediaWidget::AspectRatio239_100: vlcAspectRatio = "239:100"; break; } libvlc_video_set_aspect_ratio(vlcMediaPlayer, vlcAspectRatio); } void VlcMediaWidget::resizeToVideo(float resizeFactor) { qCDebug(logMediaWidget, "video resized to %.1f", resizeFactor); libvlc_video_set_scale(vlcMediaPlayer, resizeFactor); } void VlcMediaWidget::setDeinterlacing(MediaWidget::DeinterlaceMode deinterlacing) { const char *vlcDeinterlaceMode; switch (deinterlacing) { case MediaWidget::DeinterlaceDiscard: vlcDeinterlaceMode = "discard"; break; case MediaWidget::DeinterlaceBob: vlcDeinterlaceMode = "bob"; break; case MediaWidget::DeinterlaceLinear: vlcDeinterlaceMode = "linear"; break; case MediaWidget::DeinterlaceYadif: vlcDeinterlaceMode = "yadif"; break; case MediaWidget::DeinterlaceYadif2x: vlcDeinterlaceMode = "yadif2x"; break; case MediaWidget::DeinterlacePhosphor: vlcDeinterlaceMode = "phosphor"; break; case MediaWidget::DeinterlaceX: vlcDeinterlaceMode = "x"; break; case MediaWidget::DeinterlaceMean: vlcDeinterlaceMode = "mean"; break; case MediaWidget::DeinterlaceBlend: vlcDeinterlaceMode = "blend"; break; case MediaWidget::DeinterlaceIvtc: vlcDeinterlaceMode = "ivtc"; break; case MediaWidget::DeinterlaceDisabled: default: vlcDeinterlaceMode = NULL; } libvlc_video_set_deinterlace(vlcMediaPlayer, vlcDeinterlaceMode); } void VlcMediaWidget::play(const MediaSource &source) { addPendingUpdates(PlaybackStatus | DvdMenu); QByteArray url = source.getUrl().toEncoded(); playingDvd = false; trackNumber = 1; urlIsAudioCd = false; switch (source.getType()) { case MediaSource::Url: if (url.endsWith(".iso")) { playingDvd = true; } break; case MediaSource::AudioCd: urlIsAudioCd = true; if (url.size() >= 7) { url.replace(0, 4, "cdda"); } else { url = "cdda://"; } break; case MediaSource::VideoCd: if (url.size() >= 7) { url.replace(0, 4, "vcd"); } else { url = "vcd://"; } break; case MediaSource::Dvd: if (url.size() >= 7) { url.replace(0, 4, "dvd"); } else { url = "dvd://"; } playingDvd = true; break; case MediaSource::Dvb: break; } typeOfDevice = url.constData(); vlcMedia = libvlc_media_new_location(vlcInstance, typeOfDevice); if (urlIsAudioCd) libvlc_media_add_option(vlcMedia, "cdda-track=1"); if (makePlay() < 0) { stop(); return; } setCursor(Qt::BlankCursor); setCursor(Qt::ArrowCursor); timer->start(1000); setMouseTracking(true); } int VlcMediaWidget::makePlay() { if (vlcMedia == NULL) { libvlc_media_player_stop(vlcMediaPlayer); return -1; } libvlc_event_manager_t *eventManager = libvlc_media_event_manager(vlcMedia); libvlc_event_e eventTypes[] = { libvlc_MediaMetaChanged }; for (uint i = 0; i < (sizeof(eventTypes) / sizeof(eventTypes[0])); ++i) { if (libvlc_event_attach(eventManager, eventTypes[i], vlcEventHandler, this) != 0) { qCWarning(logMediaWidget, "Cannot attach event handler %d", eventTypes[i]); } } libvlc_media_player_set_media(vlcMediaPlayer, vlcMedia); libvlc_media_release(vlcMedia); vlcMedia = NULL; /* * FIXME: This is mostly a boilerplate as, at least with vlc 3, * this function always return -1 */ if (urlIsAudioCd) numTracks = libvlc_audio_get_track_count(vlcMediaPlayer); return libvlc_media_player_play(vlcMediaPlayer); } void VlcMediaWidget::playDirection(int direction) { QString strBuf = "cdda-track="; libvlc_state_t state; int oldTrackNumber = trackNumber; if (direction == -1) trackNumber--; else trackNumber++; if (numTracks > 0 && trackNumber > numTracks) trackNumber = numTracks; if (trackNumber < 1) trackNumber = 1; strBuf += QString::number(trackNumber); if (vlcMedia != NULL) libvlc_media_release(vlcMedia); vlcMedia = libvlc_media_new_location(vlcInstance, typeOfDevice); libvlc_media_add_option(vlcMedia, strBuf.toUtf8()); if (makePlay() < 0) stop(); do { state = libvlc_media_player_get_state (vlcMediaPlayer); } while(state != libvlc_Playing && state != libvlc_Error && state != libvlc_Ended ); if (state != libvlc_Playing) { stop(); trackNumber = oldTrackNumber; } } void VlcMediaWidget::stop() { libvlc_media_player_stop(vlcMediaPlayer); timer->stop(); setCursor(Qt::BlankCursor); setCursor(Qt::ArrowCursor); addPendingUpdates(PlaybackStatus); } void VlcMediaWidget::setPaused(bool paused) { isPaused = paused; libvlc_media_player_set_pause(vlcMediaPlayer, paused); // we don't monitor playing / buffering / paused state changes addPendingUpdates(PlaybackStatus); } void VlcMediaWidget::seek(int time) { if (!seekable) return; libvlc_media_player_set_time(vlcMediaPlayer, time); } void VlcMediaWidget::setCurrentAudioStream(int _currentAudioStream) { // skip the 'deactivate' audio channel libvlc_audio_set_track(vlcMediaPlayer, _currentAudioStream + 1); currentAudioStream = _currentAudioStream; } void VlcMediaWidget::setCurrentSubtitle(int currentSubtitle) { libvlc_track_description_t *tr, *track; int requestedSubtitle = -1; QMap::const_iterator i = subtitleId.constBegin(); while (i != subtitleId.constEnd()) { qCDebug(logVlc, "Subtitle #%d, key: %d", i.value(), i.key()); if (i.value() == currentSubtitle) { requestedSubtitle = i.key(); break; } i++; } qCDebug(logVlc, "Try to set subtitle #%d, id %d", currentSubtitle, requestedSubtitle); libvlc_video_set_spu(vlcMediaPlayer, requestedSubtitle); /* Print what it was actually selected */ tr = libvlc_video_get_spu_description(vlcMediaPlayer); track = tr; while (track != NULL) { QString subtitle = QString::fromUtf8(track->psz_name); if (subtitle.isEmpty()) { subtitle = i18n("Subtitle %1", track->i_id); } if (track->i_id == requestedSubtitle) qCDebug(logVlc, "Subtitle set to id %d: %s", track->i_id, qPrintable(subtitle)); track = track->p_next; } libvlc_track_description_list_release(tr); } void VlcMediaWidget::setExternalSubtitle(const QUrl &url) { QString fname = url.toLocalFile(); #if LIBVLC_VERSION_MAJOR > 2 if (libvlc_media_player_add_slave(vlcMediaPlayer, libvlc_media_slave_type_subtitle, url.toEncoded().constData(), true) == 0) qCWarning(logMediaWidget, "Cannot set subtitle file %s", qPrintable(fname)); #else if (libvlc_video_set_subtitle_file(vlcMediaPlayer, fname.toLocal8Bit().constData()) == 0) qCWarning(logMediaWidget, "Cannot set subtitle file %s", qPrintable(fname)); #endif } void VlcMediaWidget::setCurrentTitle(int currentTitle) { libvlc_media_player_set_title(vlcMediaPlayer, currentTitle); } void VlcMediaWidget::setCurrentChapter(int currentChapter) { libvlc_media_player_set_chapter(vlcMediaPlayer, currentChapter); } void VlcMediaWidget::setCurrentAngle(int currentAngle) { Q_UNUSED(currentAngle) // FIXME } bool VlcMediaWidget::jumpToPreviousChapter() { int currentTitle = libvlc_media_player_get_title(vlcMediaPlayer); int currentChapter = libvlc_media_player_get_chapter(vlcMediaPlayer); if (urlIsAudioCd) playDirection(-1); else libvlc_media_player_previous_chapter(vlcMediaPlayer); if ((libvlc_media_player_get_title(vlcMediaPlayer) != currentTitle) || (libvlc_media_player_get_chapter(vlcMediaPlayer) != currentChapter)) { return true; } return false; } bool VlcMediaWidget::jumpToNextChapter() { int currentTitle = libvlc_media_player_get_title(vlcMediaPlayer); int currentChapter = libvlc_media_player_get_chapter(vlcMediaPlayer); if (urlIsAudioCd) playDirection(1); else libvlc_media_player_next_chapter(vlcMediaPlayer); if ((libvlc_media_player_get_title(vlcMediaPlayer) != currentTitle) || (libvlc_media_player_get_chapter(vlcMediaPlayer) != currentChapter)) { return true; } return false; } void VlcMediaWidget::showDvdMenu() { if (playingDvd) { libvlc_media_player_set_title(vlcMediaPlayer, 0); } } int VlcMediaWidget::updatePlaybackStatus() { MediaWidget::PlaybackStatus oldPlaybackStatus = playbackStatus; switch (libvlc_media_player_get_state(vlcMediaPlayer)) { case libvlc_NothingSpecial: case libvlc_Stopped: playbackStatus = MediaWidget::Idle; break; case libvlc_Opening: case libvlc_Buffering: playbackStatus = MediaWidget::Playing; break; case libvlc_Playing: // The first time libVLC is set to pause, it reports status as playing if (isPaused) playbackStatus = MediaWidget::Paused; else playbackStatus = MediaWidget::Playing; break; case libvlc_Paused: playbackStatus = MediaWidget::Paused; break; case libvlc_Ended: playDirection(1); break; case libvlc_Error: playbackStatus = MediaWidget::Idle; // don't keep last picture shown libvlc_media_player_stop(vlcMediaPlayer); break; } if (playbackStatus == MediaWidget::Idle) { addPendingUpdates(DvdMenu); playingDvd = false; } // Report if the status has changed return (oldPlaybackStatus != playbackStatus); } void VlcMediaWidget::updateCurrentTotalTime() { if (playbackStatus == MediaWidget::Idle) return; currentTime = int(libvlc_media_player_get_time(vlcMediaPlayer)); totalTime = int(libvlc_media_player_get_length(vlcMediaPlayer)); if (currentTime < 0) { currentTime = 0; } if (totalTime < 0) { totalTime = 0; } if (totalTime && currentTime > totalTime) { currentTime = totalTime; } } void VlcMediaWidget::updateSeekable() { seekable = libvlc_media_player_is_seekable(vlcMediaPlayer); } void VlcMediaWidget::updateMetadata() { metadata.clear(); libvlc_media_t *media = libvlc_media_player_get_media(vlcMediaPlayer); if (media != NULL) { metadata.insert(MediaWidget::Title, QString::fromUtf8(libvlc_media_get_meta(media, libvlc_meta_Title))); metadata.insert(MediaWidget::Artist, QString::fromUtf8(libvlc_media_get_meta(media, libvlc_meta_Artist))); metadata.insert(MediaWidget::Album, QString::fromUtf8(libvlc_media_get_meta(media, libvlc_meta_Album))); metadata.insert(MediaWidget::TrackNumber, QString::fromUtf8(libvlc_media_get_meta(media, libvlc_meta_TrackNumber))); } if (urlIsAudioCd) metadata.insert(MediaWidget::Title, i18n("CD track ") + QString::number(trackNumber)); } void VlcMediaWidget::updateAudioStreams() { audioStreams.clear(); libvlc_track_description_t *tr, *track; tr = libvlc_audio_get_track_description(vlcMediaPlayer); track = tr; if (track != NULL) { // skip the 'deactivate' audio channel track = track->p_next; } while (track != NULL) { QString audioStream = QString::fromUtf8(track->psz_name); int cutBegin = (audioStream.indexOf(QLatin1Char('[')) + 1); if (cutBegin > 0) { int cutEnd = audioStream.lastIndexOf(QLatin1Char(']')); if (cutEnd >= 0) { // remove unnecessary text audioStream = audioStream.mid(cutBegin, cutEnd - cutBegin); } } if (audioStream.isEmpty()) { audioStream = QString::number(audioStreams.size() + 1); } audioStreams.append(audioStream); track = track->p_next; } libvlc_track_description_list_release(tr); // skip the 'deactivate' audio channel currentAudioStream = (libvlc_audio_get_track(vlcMediaPlayer) - 1); } void VlcMediaWidget::updateSubtitles() { libvlc_track_description_t *tr, *track; int i = 0; subtitles.clear(); subtitleId.clear(); tr = libvlc_video_get_spu_description(vlcMediaPlayer); track = tr; if (track != NULL) { // skip the 'deactivate' subtitle track = track->p_next; } while (track != NULL) { QString subtitle = QString::fromUtf8(track->psz_name); if (subtitle.isEmpty()) { subtitle = i18n("Subtitle %1", track->i_id); } // 0 is reserved for "disabled" at mediawidget. So, we should // Start counting from 1, to match the range expected for // currentSubtitle subtitleId[track->i_id] = ++i; subtitles.append(subtitle); qCDebug(logVlc, "Got subtitle id#%d: %s", track->i_id, qPrintable(subtitle)); track = track->p_next; } libvlc_track_description_list_release(tr); // skip the 'deactivate' subtitle currentSubtitle = subtitleId.value(libvlc_video_get_spu(vlcMediaPlayer), -1); } void VlcMediaWidget::updateTitles() { titleCount = libvlc_media_player_get_title_count(vlcMediaPlayer); currentTitle = libvlc_media_player_get_title(vlcMediaPlayer); } void VlcMediaWidget::updateChapters() { chapterCount = libvlc_media_player_get_chapter_count(vlcMediaPlayer); currentChapter = libvlc_media_player_get_chapter(vlcMediaPlayer); } void VlcMediaWidget::updateAngles() { // FIXME } void VlcMediaWidget::updateDvdMenu() { dvdMenu = playingDvd; } void VlcMediaWidget::updateVideoSize() { // FIXME } void VlcMediaWidget::dvdNavigate(int key) { int event; switch (key){ case Qt::Key_Return: event = libvlc_navigate_activate; break; case Qt::Key_Up: event = libvlc_navigate_up; break; case Qt::Key_Down: event = libvlc_navigate_down; break; case Qt::Key_Left: event = libvlc_navigate_left; break; case Qt::Key_Right: event = libvlc_navigate_right; break; default: return; } libvlc_media_player_navigate(vlcMediaPlayer, event); } void VlcMediaWidget::mousePressEvent(QMouseEvent *event) { AbstractMediaWidget::mousePressEvent(event); } void VlcMediaWidget::hideMouse() { timer->stop(); setCursor(Qt::ArrowCursor); setCursor(Qt::BlankCursor); } void VlcMediaWidget::mouseMoveEvent(QMouseEvent *event) { if (!timer->isActive()) { setCursor(Qt::BlankCursor); setCursor(Qt::ArrowCursor); } if (this->underMouse()) timer->start(1000); else timer->stop(); AbstractMediaWidget::mouseMoveEvent(event); } void VlcMediaWidget::vlcEvent(const libvlc_event_t *event) { PendingUpdates pendingUpdatesToBeAdded = 0; switch (event->type) { #if LIBVLC_VERSION_MAJOR > 2 case libvlc_MediaPlayerESAdded: case libvlc_MediaPlayerESDeleted: #endif case libvlc_MediaMetaChanged: - pendingUpdatesToBeAdded = Metadata | Subtitles; + pendingUpdatesToBeAdded = Metadata | Subtitles | AudioStreams; break; case libvlc_MediaPlayerEncounteredError: pendingUpdatesToBeAdded = PlaybackStatus; break; case libvlc_MediaPlayerEndReached: pendingUpdatesToBeAdded = (PlaybackFinished | PlaybackStatus); break; case libvlc_MediaPlayerLengthChanged: pendingUpdatesToBeAdded = CurrentTotalTime; break; case libvlc_MediaPlayerSeekableChanged: pendingUpdatesToBeAdded = Seekable | Subtitles; break; case libvlc_MediaPlayerStopped: pendingUpdatesToBeAdded = PlaybackStatus; setMouseTracking(false); break; case libvlc_MediaPlayerTimeChanged: pendingUpdatesToBeAdded = CurrentTotalTime; break; } if (pendingUpdatesToBeAdded != 0) { addPendingUpdates(pendingUpdatesToBeAdded); } else { qCWarning(logMediaWidget, "Unknown libVLC event type %d", event->type); } } void VlcMediaWidget::vlcEventHandler(const libvlc_event_t *event, void *instance) { reinterpret_cast(instance)->vlcEvent(event); }