/** * @RDE_HEADER@ * Handle Audio/Video output for thp through libvlc * $Id$ * $HeadURL$ * $Author$ */ #include "audio_video_widget.h" #include "audio_video_widget_p.h" #include "ui_audio_video_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace VideoImport { static const float s_speedArray[] = { 1. / 8., 1. / 4., 1. / 2., 1., 2., 4., 8. }; /** * AudioVideoWidget::Private */ AudioVideoWidget::Private::Private(QFrame *parent) : m_vlcinstance(nullptr) , m_mPlayer(nullptr) , m_mListPlayer(nullptr) , m_mediaList(nullptr) , currentSpeedIdx(3) , m_parent(parent) , m_timer(new QTimer) , m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("VideoImport.AudioVideoWidget")) { init(); connect(ui->btnSlower, &QToolButton::clicked, this, &Private::onSlower); connect(ui->btnFaster, &QToolButton::clicked, this, &Private::onFaster); connect(ui->btn100pct, &QToolButton::clicked, this, &Private::on100pct); connect(ui->btnPlay, &QToolButton::clicked, this, &Private::onPlay); connect(ui->btnStop, &QToolButton::clicked, this, &Private::onStop); connect(ui->btnCut, &QPushButton::clicked, this, &Private::onCut); connect(ui->sldPosition, &QSlider::valueChanged, this, &Private::onPositionChanged); connect(ui->sldRange, &LtsWidgets::LtsRangeSlider::maximumValueChanged, this, &Private::onRangeMaximumChanged); connect(ui->sldRange, &LtsWidgets::LtsRangeSlider::minimumValueChanged, this, &Private::onRangeMinimumChanged); // connect(ui->sldPosition, &LtsWidgets::LtsRangeSlider::minimumValueChanged, this, &AudioVideoWidget::Private::onPositionChanged); connect(ui->sldVolume, &QSlider::sliderMoved, this, &Private::onVolumeChanged); connect(m_timer, &QTimer::timeout, this, &Private::onUpdateGUI); m_initThread = new AudioVideoInitThread(this); connect(m_initThread, &AudioVideoInitThread::finished, this, &Private::onVLCInstanceInitializd); m_initThread->start(QThread::LowPriority); ui->sldRange->setEnabled(false); ui->sldPosition->setEnabled(false); } AudioVideoWidget::Private::~Private() { uninitVLCPlayer(); uninitVLCInstance(); delete ui; delete m_timer; } bool AudioVideoWidget::Private::init() { if (!m_parent) return false; ui = new Ui::audioVideoWidget(); ui->setupUi(m_parent); QPixmap pm(10, 10); pm.fill(Qt::black); ui->outputWidget->setPixmap(pm); ui->outputWidget->setScaledContents(true); ui->sldRange->setMaximum(1000); ui->sldRange->setMaximumValue(1000); ui->sldPosition->setMaximum(1000); ui->sldVolume->setMaximum(100); return true; } void AudioVideoWidget::Private::onVLCInstanceInitializd() { m_initThread->deleteLater(); m_initThread = nullptr; } void AudioVideoWidget::Private::setAV(bool isAudio) { if (isAudio) { delete ui->btn100pct; delete ui->btnFaster; delete ui->btnSlower; delete ui->outputWidget; ui->outputWidget = nullptr; } else { // delete ui->verticalSpacer; ui->verticalSpacer = 0; delete ui->lblAudio; } } static QMutex *serializeVLCInit() { static QMutex s_serializeVLCInit; return &s_serializeVLCInit; } bool AudioVideoWidget::Private::initVLCInstance(bool bInInitThread) { QMutexLocker l(serializeVLCInit()); if (!bInInitThread && m_initThread != nullptr) { QLabel myLabel; myLabel.setWindowTitle(tr("Loading VLC player...")); myLabel.setText(tr("Waiting until VLC player is loaded")); QRect r = QGuiApplication::primaryScreen()->availableGeometry(); myLabel.show(); myLabel.move(r.width() / 2 - myLabel.width() / 2, r.height() / 2 - myLabel.height() / 2); myLabel.setWindowIcon(QIcon(QLatin1String(":icons/16x16/rde.ico"))); while (m_initThread) QCoreApplication::processEvents(); } if (m_vlcinstance != nullptr) return true; //preparation of the vlc command const char *vlc_argv[20]; int iCount = 0; vlc_argv[iCount++] = "-I"; vlc_argv[iCount++] = "dummy"; // Don't use any interface // vlc_argv[iCount++] = "--ignore-config"; // Don't use VLC's config #ifdef LTS_DEBUG vlc_argv[iCount++] = "--extraintf=logger"; // log anything vlc_argv[iCount++] = "--verbose=2"; // be much more verbose then normal for debugging purpose #else vlc_argv[iCount++] = "--quiet"; #endif if (bInInitThread) vlc_argv[iCount++] = "--no-media-library"; vlc_argv[iCount++] = "--"; vlc_argv[iCount] = nullptr; //Initialize an instance of vlc //create a new libvlc instance m_vlcinstance = libvlc_new(iCount, vlc_argv); //tricky calculation of the char space used if (!m_vlcinstance) ERROR_LOG(m_logger) << "Error initializing libVLC - unable to play audio or video"; return (m_vlcinstance != nullptr); } bool AudioVideoWidget::Private::uninitVLCInstance() { QMutexLocker l(serializeVLCInit()); if (!uninitVLCPlayer()) return false; if (m_vlcinstance) { libvlc_release(m_vlcinstance); m_vlcinstance = nullptr; } return true; } bool AudioVideoWidget::Private::initVLCPlayer() { DEBUG_LOG(m_logger) << "Initializing libVLC"; if (m_mPlayer) { if (!ui) return false; ui->sldVolume->setValue(libvlc_audio_get_volume(m_mPlayer)); return true; } initVLCInstance(); //Initialize an instance of vlc //Create a media player playing environement m_mPlayer = libvlc_media_player_new(m_vlcinstance); m_mListPlayer = libvlc_media_list_player_new(m_vlcinstance); m_mediaList = libvlc_media_list_new(m_vlcinstance); libvlc_media_list_player_set_media_player(m_mListPlayer, m_mPlayer); libvlc_media_list_player_set_media_list(m_mListPlayer, m_mediaList); ui->sldVolume->setValue(libvlc_audio_get_volume(m_mPlayer)); return true; } bool AudioVideoWidget::Private::uninitVLCPlayer() { DEBUG_LOG(m_logger) << "Uninitializing libVLC"; if (m_mListPlayer) { /* Stop playing */ libvlc_media_list_player_stop(m_mListPlayer); /* Free the media_player */ libvlc_media_list_player_release(m_mListPlayer); m_mListPlayer = nullptr; } if (m_mediaList) { clearMediaList(); libvlc_media_list_release(m_mediaList); m_mediaList = nullptr; } if (m_mPlayer) { /* Stop playing */ libvlc_media_player_stop(m_mPlayer); /* Free the media_player */ libvlc_media_player_release(m_mPlayer); m_mPlayer = nullptr; } return true; } void AudioVideoWidget::Private::clearMediaList() { DEBUG_LOG(m_logger) << "Clearing current playlist"; if (!m_mediaList) return; libvlc_media_list_lock(m_mediaList); const int cnt = libvlc_media_list_count(m_mediaList); for (int i = cnt - 1; i >= 0; --i) libvlc_media_list_remove_index(m_mediaList, i); libvlc_media_list_unlock(m_mediaList); } bool AudioVideoWidget::Private::playVLC(const QUrl &url) { if (!ui) return false; DEBUG_LOG(m_logger) << "Try to play " << url.toString(); const QString path = url.toLocalFile(); if (path.isEmpty()) return false; clearMediaList(); libvlc_media_list_lock(m_mediaList); QFileInfo fi(path); DEBUG_LOG(m_logger) << "path: " << path << " isFile: " << fi.isFile(); if (fi.isFile()) { const QByteArray pNative = QDir::toNativeSeparators(path).toLocal8Bit(); libvlc_media_t *media = libvlc_media_new_path(m_vlcinstance, pNative.data()); if (!media) { ERROR_LOG(m_logger) << "Error setting media to '" << pNative << "'"; return false; } libvlc_media_list_add_media(m_mediaList, media); // Todo: streams is always 0, although media was parsed. Why? // If frames can be obtained: Both sliders should be as large as number of frames. // libvlc_media_parse(media); // if (libvlc_media_is_parsed(media)) // { // libvlc_media_track_t **tracks; // double framerate = 1; // unsigned streams = libvlc_media_tracks_get(media, &tracks); // for (unsigned i = 0; i < streams; ++i) // { // libvlc_video_track_t *video = tracks[i]->video; // if (video) // framerate = 1. * video->i_frame_rate_num / video->i_frame_rate_den; // } // libvlc_media_tracks_release(tracks, streams); // m_frames = floor(1000. * libvlc_media_get_duration(media) * framerate); // } // else { // m_frames = 1000; // } libvlc_media_release(media); // libvlc_media_new_location(), ref-counting! } else { ERROR_LOG(m_logger) << "Unknown media given: " << path; libvlc_media_list_unlock(m_mediaList); return false; } libvlc_media_list_unlock(m_mediaList); if (ui->outputWidget) { #if defined(Q_OS_WIN) libvlc_media_player_set_hwnd(m_mPlayer, (HWND)ui->outputWidget->winId()); #elif defined(Q_OS_MAC) libvlc_media_player_set_agl(m_mPlayer, ui->outputWidget->winId()) ; #else //Linux libvlc_media_player_set_xwindow(m_mPlayer, ui->outputWidget->winId()); #endif } ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png"))); ui->btnPlay->setEnabled(true); // ui->sldPosition->setMaximum(m_frames); ui->sldPosition->setEnabled(false); // ui->sldRange->setMaximum(m_frames); ui->sldRange->setEnabled(false); ui->btnStop->setEnabled(true); onRangeMaximumChanged(0); // Initialize Min/Max currentSpeedIdx = 3; QTimer::singleShot(0, this, &AudioVideoWidget::Private::onPlay); return true; } bool AudioVideoWidget::Private::stopVLC() { DEBUG_LOG(m_logger) << "Stop playing"; QTimer::singleShot(0, this, SLOT(onStop())); return true; } void AudioVideoWidget::Private::setRate() { DEBUG_LOG(m_logger) << "Setting speed to " << s_speedArray[currentSpeedIdx]; libvlc_state_t state = getState(); if (state == libvlc_Playing || state == libvlc_Paused) libvlc_media_player_set_rate(m_mPlayer, s_speedArray[currentSpeedIdx]); } libvlc_state_t AudioVideoWidget::Private::getState() const { libvlc_state_t s = libvlc_media_list_player_get_state(m_mListPlayer); static libvlc_state_t lastState = libvlc_NothingSpecial; if (s == lastState) return s; lastState = s; if (s == libvlc_Error) ERROR_LOG(m_logger) << "libVLC error: " << libvlc_errmsg(); return s; } void AudioVideoWidget::Private::onSlower() { if (currentSpeedIdx == 0) return; --currentSpeedIdx; setRate(); onUpdateGUI(); } void AudioVideoWidget::Private::onFaster() { if (currentSpeedIdx == 6) return; ++currentSpeedIdx; setRate(); onUpdateGUI(); } void AudioVideoWidget::Private::on100pct() { currentSpeedIdx = 3; setRate(); onUpdateGUI(); } void AudioVideoWidget::Private::onPlay() { if (libvlc_media_list_player_is_playing(m_mListPlayer)) { DEBUG_LOG(m_logger) << "Pause playing"; libvlc_media_list_player_pause(m_mListPlayer); ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png"))); m_timer->stop(); onUpdateGUIWithState(libvlc_Paused); } else { DEBUG_LOG(m_logger) << "Start playing"; ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-pause.png"))); ui->sldPosition->setEnabled(true); ui->sldRange->setEnabled(true); libvlc_media_list_player_play(m_mListPlayer); setRate(); m_timer->start(100); onUpdateGUIWithState(libvlc_Playing); } } void AudioVideoWidget::Private::onStop() { if (!m_mListPlayer) return; libvlc_state_t state = getState(); if (state == libvlc_Stopped) return; DEBUG_LOG(m_logger) << "Stop playing"; libvlc_media_list_player_stop(m_mListPlayer); onUpdateGUI(); } void AudioVideoWidget::Private::onCut() { double maxTimeSecs = libvlc_media_player_get_length(m_mPlayer) / 1000.; double minimumRelative = ui->sldRange->minimumValue() / 1000.; double maximumRelative = ui->sldRange->maximumValue() / 1000.; QString inTime = timeString(maxTimeSecs * minimumRelative, true); QString duration = timeString(maxTimeSecs * (maximumRelative - minimumRelative), true); emit signalCut(inTime, duration); } void AudioVideoWidget::Private::onPositionChanged(int newPosition) { if (newPosition < ui->sldRange->minimumPosition()) { ui->sldPosition->setValue(ui->sldRange->minimumValue()); return; } if (newPosition > ui->sldRange->maximumPosition()) { ui->sldPosition->setValue(ui->sldRange->maximumValue()); return; } libvlc_state_t state = getState(); DEBUG_LOG(m_logger) << "Slider changed - going to new position"; if (state != libvlc_Playing && state != libvlc_Paused) return; // Position geht von 0..1, Slider hat 1000 Steps // Todo: if slider is different than 1000 steps, correct here: libvlc_media_player_set_position(m_mPlayer, newPosition / 1000.); onUpdateGUI(); } void AudioVideoWidget::Private::onRangeMinimumChanged(int newMinimum) { if (ui->sldPosition->value() < newMinimum) ui->sldPosition->setValue(newMinimum); double maxTimeSecs = libvlc_media_player_get_length(m_mPlayer) / 1000.; double minimumRelative = newMinimum / 1000.; double maximumRelative = ui->sldRange->maximumValue() / 1000.; ui->lblMinMax->setText(timeString(maxTimeSecs * minimumRelative) + QLatin1Literal(" / ") + timeString(maxTimeSecs * maximumRelative)); } void AudioVideoWidget::Private::onRangeMaximumChanged(int newMaximum) { if (ui->sldPosition->value() > newMaximum) ui->sldPosition->setValue(newMaximum); double maxTimeSecs = libvlc_media_player_get_length(m_mPlayer) / 1000.; double minimumRelative = ui->sldRange->minimumValue() / 1000.; double maximumRelative = newMaximum / 1000.; ui->lblMinMax->setText(timeString(maxTimeSecs * minimumRelative) + QLatin1Literal(" / ") + timeString(maxTimeSecs * maximumRelative)); } void AudioVideoWidget::Private::onVolumeChanged(int newVol) { libvlc_audio_set_volume(m_mPlayer, newVol); onUpdateGUI(); } void AudioVideoWidget::Private::onUpdateGUI() { onUpdateGUIWithState(libvlc_NothingSpecial); } QString AudioVideoWidget::Private::timeString(double curTime, bool exact) { const int minutesCur = floor(curTime / 60.); const int secondsCur = curTime - 60 * minutesCur; const int millies = floor((curTime - floor(curTime)) * 1000); char timeStr[32]; if (exact) snprintf(timeStr, sizeof(timeStr), "%02d:%02d.%03d", minutesCur, secondsCur, millies); else snprintf(timeStr, sizeof(timeStr), "%02d:%02d", minutesCur, secondsCur); return QString::fromLatin1(timeStr); } void AudioVideoWidget::Private::onUpdateGUIWithState(libvlc_state_t overwrite_state) { libvlc_state_t state = overwrite_state; if (state == libvlc_NothingSpecial) state = getState(); DEBUG_LOG(m_logger) << "onUpdateGUI for state " << state; if (state == libvlc_Playing || state == libvlc_Paused) { double maxTime = libvlc_media_player_get_length(m_mPlayer) / 1000.; if (maxTime > 0.1) { libvlc_media_list_lock(m_mediaList); const int cnt = libvlc_media_list_count(m_mediaList); for (int i = cnt - 1; i >= 0; --i) { libvlc_media_t *media = libvlc_media_list_item_at_index(m_mediaList, i); DEBUG_LOG(m_logger) << "Duration: " << libvlc_media_get_duration(media); libvlc_media_release(media); } libvlc_media_list_unlock(m_mediaList); } double curTime = libvlc_media_player_get_time(m_mPlayer) / 1000.; auto curPos = libvlc_media_player_get_position(m_mPlayer); // Slider ist in 1000 Schritte eingeteilt ui->sldPosition->setValue(qRound(curPos * 1000)); ui->lblPosition->setText(timeString(curTime) + QLatin1Literal(" / ") + timeString(maxTime)); if (state == libvlc_Playing && ui->sldPosition->value() >= ui->sldRange->maximumValue()) onPlay(); // -> Pause } else if (state == libvlc_Stopped || state == libvlc_Ended) { // wir können nicht onStop() aufrufen -> rekursion! bool bStop = true; if (state == libvlc_Ended) { // Timer und VLC *nicht* stoppen da der aktuelle State ggf. genau zwischen den einzelnen // Video-Schnipseln einer Playlist abgefragt wurde // --> prüfen, ob es das letzte Video der Playlist war libvlc_media_t *media = libvlc_media_player_get_media(m_mPlayer); if (media != nullptr) { const int cnt = libvlc_media_list_count(m_mediaList); if (cnt > 0) { libvlc_media_t *media2 = libvlc_media_list_item_at_index(m_mediaList, cnt - 1); bStop = (media2 == media); libvlc_media_release(media2); } libvlc_media_release(media); } if (bStop) libvlc_media_list_player_stop(m_mListPlayer); } if (bStop) { ui->btnPlay->setIcon(QIcon(QStringLiteral(":icons/16x16/media-playback-start.png"))); ui->sldPosition->setEnabled(false); ui->sldRange->setEnabled(false); m_timer->stop(); ui->sldPosition->setValue(ui->sldRange->minimumValue()); ui->lblPosition->setText(QLatin1String("--:-- / --:--")); } } QString speedStr = QStringLiteral("%1x").arg(s_speedArray[currentSpeedIdx], 4, 'f', 2); ui->lblSpeed->setText(speedStr); } /** * AudioVideoInitThread */ AudioVideoInitThread::AudioVideoInitThread(AudioVideoWidget::Private *p) : m_private(p) {} void AudioVideoInitThread::run() { m_private->initVLCInstance(true); } /** * AudioVideoWidget */ AudioVideoWidget::AudioVideoWidget(QWidget *parent) : QFrame(parent) , d(new Private(this)) { connect(d, &AudioVideoWidget::Private::signalCut, this, &AudioVideoWidget::onCut); } void AudioVideoWidget::onCut(const QString &inTime, const QString &duration) { const QString localFile = m_url.toLocalFile(); QString extension = localFile.mid(localFile.lastIndexOf(QLatin1Char('.')) + 1); QUrl outUrl = QFileDialog::getSaveFileUrl(this, tr("Save Video File"), QUrl(), tr("Source File Format (*.%1)").arg(extension)); // ffmpeg -ss [inTime] -t [outTime] -i [m_url] [url] QString command = tr("ffmpeg -i \"%3\" -ss %1 -t %2 -acodec copy -vcodec copy \"%4\"").arg(inTime, duration, m_url.toLocalFile(), outUrl.toLocalFile()); QProcess::startDetached(command); } AudioVideoWidget::~AudioVideoWidget() { delete d; } bool AudioVideoWidget::play(const QUrl &file) { d->initVLCPlayer(); setEnabled(true); return d->playVLC(file); } bool AudioVideoWidget::stop() { return d->stopVLC(); } void AudioVideoWidget::setAV(bool isAudio) { d->setAV(isAudio); } void AudioVideoWidget::onOpenFile() { QString folder = QString::fromLatin1(qgetenv("USER")); if (folder.isEmpty()) folder = QString::fromLatin1(qgetenv("USERNAME")); folder.prepend(QLatin1String("/run/media/")); m_url = QFileDialog::getOpenFileUrl(this, tr("Open Video File"), QUrl::fromLocalFile(folder), tr("Video files (*.*)")); play(m_url); } } // namespace VideoImport