Files
qt5-legatus-legacy/Lts/Application/THP/thpwidgets/thp_audio_video_widget.cpp
T
2026-08-13 18:33:10 +02:00

630 lines
17 KiB
C++
Executable File

/**
* @RDE_HEADER@
* Handle Audio/Video output for thp through libvlc
* $Id$
* $HeadURL$
* $Author$
*/
#include "thp_audio_video_widget.h"
#include "thp_audio_video_widget_p.h"
#include "ui_thp_audio_video_widget.h"
#include <QCoreApplication>
#include <QDirIterator>
#include <QFileInfo>
#include <QMutex>
#include <QScreen>
#include <QTimer>
#include <QUrl>
#include <QGuiApplication>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <math.h>
#include <stdio.h>
#include <vlc/libvlc_version.h>
namespace THP
{
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("THP.AudioVideoWidget"))
{
init();
connect(ui->btnSlower, &QToolButton::clicked, this, &AudioVideoWidget::Private::onSlower);
connect(ui->btnFaster, &QToolButton::clicked, this, &AudioVideoWidget::Private::onFaster);
connect(ui->btn100pct, &QToolButton::clicked, this, &AudioVideoWidget::Private::on100pct);
connect(ui->btnPlay, &QToolButton::clicked, this, &AudioVideoWidget::Private::onPlay);
connect(ui->btnStop, &QToolButton::clicked, this, &AudioVideoWidget::Private::onStop);
connect(ui->sldPosition, &QSlider::sliderMoved, this, &AudioVideoWidget::Private::onPositionChanged);
connect(ui->sldVolume, &QSlider::sliderMoved, this, &AudioVideoWidget::Private::onVolumeChanged);
connect(m_timer, &QTimer::timeout, this, &AudioVideoWidget::Private::onUpdateGUI);
m_initThread = new AudioVideoInitThread(this);
connect(m_initThread, &AudioVideoInitThread::finished, this, &AudioVideoWidget::Private::onVLCInstanceInitializd);
m_initThread->start(QThread::LowPriority);
}
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->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() && path.endsWith(QLatin1String(".pls")))
{
// pls parsing scheint nicht mehr richtig zu funktionieren - selbst machen
DEBUG_LOG(m_logger) << "Parse Playlist";
QFile f(path);
if (f.open(QIODevice::ReadOnly))
{
DEBUG_LOG(m_logger) << "Parse Playlist2 " << f.canReadLine();
QMap<int, QByteArray> fileMap;
QByteArray baAll = f.readAll();
const QList<QByteArray> lines = baAll.split('\n');
for (const QByteArray &line : lines)
{
QByteArray ba = line.trimmed();
DEBUG_LOG(m_logger) << "ba: " << ba;
if (!ba.startsWith("File "))
continue;
ba = ba.mid(qstrlen("File "));
int idx = ba.indexOf('=');
DEBUG_LOG(m_logger) << "idx: " << idx;
if (idx < 0)
continue;
bool bOk;
int id = ba.left(idx).toInt(&bOk);
DEBUG_LOG(m_logger) << "id: " << id << " bOk: " << bOk << " str: " << ba.left(idx);
if (!bOk)
continue;
QByteArray filename = ba.mid(idx + 1);
DEBUG_LOG(m_logger) << "filename: " << filename;
fileMap.insertMulti(id, fi.absolutePath().toLocal8Bit() + '/' + filename);
}
for (const QByteArray &fn : fileMap)
{
const QByteArray pNative = QDir::toNativeSeparators(QString::fromLocal8Bit(fn)).toLocal8Bit();
DEBUG_LOG(m_logger) << "pNative: " << pNative;
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;
}
//add option to disable hardware acceleration
libvlc_media_add_option(media, ":avcodec-hw=none");
libvlc_media_list_add_media(m_mediaList, media);
#if LIBVLC_VERSION_MAJOR > 2
libvlc_media_parse_with_options(media, libvlc_media_parse_local, 0);
#else
libvlc_media_parse(media);
#endif
libvlc_media_release(media); // libvlc_media_new_location(), ref-counting!
}
}
}
else 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);
libvlc_media_release(media); // libvlc_media_new_location(), ref-counting!
}
else if (fi.isDir())
{
QDirIterator dirIt(fi.absoluteFilePath());
while (dirIt.hasNext())
{
const QString fn = dirIt.next();
fi.setFile(fn);
if (!fi.isFile())
continue;
const QByteArray pNative = QDir::toNativeSeparators(fn).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);
#if LIBVLC_VERSION_MAJOR > 2
libvlc_media_parse_with_options(media, libvlc_media_parse_local, 0);
#else
libvlc_media_parse(media);
#endif
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->setEnabled(false);
ui->btnStop->setEnabled(true);
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, &AudioVideoWidget::Private::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);
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::onPositionChanged(int newPosition)
{
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
libvlc_media_player_set_position(m_mPlayer, newPosition / 1000.);
onUpdateGUI();
}
void AudioVideoWidget::Private::onVolumeChanged(int newVol)
{
libvlc_audio_set_volume(m_mPlayer, newVol);
onUpdateGUI();
}
void AudioVideoWidget::Private::onUpdateGUI()
{
onUpdateGUIWithState(libvlc_NothingSpecial);
}
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));
const int minutesCur = floor(curTime / 60.);
const int secondsCur = curTime - 60 * minutesCur;
const int minutesAbs = floor(maxTime / 60.);
const int secondsAbs = maxTime - 60 * minutesAbs;
char timeStr[64];
snprintf(timeStr, sizeof(timeStr), "%02d:%02d / %02d:%02d", minutesCur, secondsCur, minutesAbs, secondsAbs);
ui->lblPosition->setText(QString::fromLatin1(timeStr));
}
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);
m_timer->stop();
ui->sldPosition->setValue(0);
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))
{}
AudioVideoWidget::~AudioVideoWidget()
{
delete d;
}
bool AudioVideoWidget::play(const QUrl &file)
{
d->initVLCPlayer();
return d->playVLC(file);
}
bool AudioVideoWidget::stop()
{
return d->stopVLC();
}
void AudioVideoWidget::setAV(bool isAudio)
{
d->setAV(isAudio);
}
} // namespace THP