- Formatted all CodeGen module files (9 files) - Formatted LtsAppDescriptor module files - Formatted LtsAppManager/AppChooser files - Applied clang-format to all DTC and enum headers across modules - Removed coordTransTests from CommonUnitTestsPlaceholder (moved to tests/) Cosmetic changes only, no functional modifications. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
442 lines
12 KiB
C++
442 lines
12 KiB
C++
|
|
|
|
#include "topic_replay.h"
|
|
|
|
#include <AudioVideoManagement/ava_control.h>
|
|
#include <AudioVideoManagement/ava_session.h>
|
|
#include <AudioVideoManagement/ava_types.h>
|
|
#include <AudioVideoManagement/avm_data_plugin_interface.h>
|
|
#include <AudioVideoManagement/vd_control.h>
|
|
#include <AudioVideoManagement/vd_session.h>
|
|
#include <LtsUtilsCore/json_object.h>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QJsonDocument>
|
|
#include <QRect>
|
|
#include <QWidget>
|
|
#include <QtGlobal>
|
|
#include <TopicEditorBase/topic_editor_base.h>
|
|
#include <chrono>
|
|
#include <nglsettings/nglsettings_settings.h>
|
|
#include <vector>
|
|
|
|
namespace AudioVideoManagement
|
|
{
|
|
|
|
class TopicReplay::ExportScript : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
ExportScript(const QString& topLevelExportDir, TopicEditorBase::ExportNotifier* exportStep);
|
|
|
|
public slots:
|
|
void avScriptFinished(const QString& exportDir, AvaStreamType streamType, bool success);
|
|
|
|
private:
|
|
QString audioVideoPlaylistErstellen(const QString& dir, AvaStreamType streamType);
|
|
bool writeFile(const QByteArray& data, const QString& fileName);
|
|
|
|
QString m_topLevelExportDir;
|
|
TopicEditorBase::ExportNotifier* m_exportStep;
|
|
};
|
|
|
|
TopicReplay::TopicReplay(DataInterface* dataIfc, bool isAudio) : m_isAudio(isAudio), m_dataIfc(dataIfc)
|
|
{
|
|
DEBUG_LOG(m_logger) << "TopicReplay::TopicReplay()";
|
|
Q_ASSERT(dataIfc);
|
|
}
|
|
|
|
TopicReplay::~TopicReplay()
|
|
{
|
|
DEBUG_LOG(m_logger) << "TopicReplay::~TopicReplay()";
|
|
|
|
if (m_isPlaying)
|
|
playbackStop();
|
|
}
|
|
|
|
QWidget* TopicReplay::createWidget()
|
|
{
|
|
DEBUG_LOG(m_logger) << "TopicReplay::createWidget()";
|
|
Q_ASSERT(!m_screenPlacementHelper);
|
|
|
|
if (m_isAudio)
|
|
return nullptr;
|
|
|
|
m_screenPlacementHelper.reset(new ScreenPlacementHelper());
|
|
connect(m_screenPlacementHelper.data(), &ScreenPlacementHelper::widgetPlaced, this, &TopicReplay::onWidgetPlaced);
|
|
return m_screenPlacementHelper.data();
|
|
}
|
|
|
|
void TopicReplay::slotSetScriptResponse(const AudioVideoManagement::AvaStreamInfo& streamInfo,
|
|
AudioVideoManagement::AvaStreamState streamState)
|
|
{
|
|
if (streamState == AudioVideoManagement::AvaStreamState::NotPossible)
|
|
ERROR_LOG(m_logger) << "Script could not be set";
|
|
else
|
|
ERROR_LOG(m_logger) << "Script set!";
|
|
|
|
ERROR_LOG(m_logger) << "streamInfo: start" << streamInfo.duration.count();
|
|
ERROR_LOG(m_logger) << "streamState: " << static_cast<int>(streamState);
|
|
}
|
|
|
|
bool TopicReplay::playbackInit(const QJsonObject& attributes)
|
|
{
|
|
DEBUG_LOG(m_logger) << "TopicReplay::playbackInit()";
|
|
DEBUG_LOG(m_logger) << QJsonDocument(attributes).toJson();
|
|
|
|
if (m_screenPlacementHelper && !m_isAudio)
|
|
m_screenPlacementHelper->show();
|
|
|
|
m_playbackAttributes = attributes;
|
|
|
|
AvaControl* ctrl = getAvaControl();
|
|
if (ctrl == nullptr)
|
|
{
|
|
ERROR_LOG(m_logger) << "no ava control!";
|
|
return false;
|
|
}
|
|
|
|
AvaTopicData topicData(m_playbackAttributes);
|
|
|
|
if (!topicData.isValid)
|
|
{
|
|
ERROR_LOG(m_logger) << "topicData is invalid";
|
|
return false;
|
|
}
|
|
|
|
m_scriptName = topicData.scriptName();
|
|
|
|
Q_ASSERT(topicData.isAudio() == m_isAudio);
|
|
|
|
PlaybackApp* app = m_isAudio ? m_dataIfc->audioPresentationApp() : m_dataIfc->videoPresentationApp();
|
|
if (app == nullptr || app->inUse())
|
|
{
|
|
ERROR_LOG(m_logger) << "app invalid or in use!";
|
|
return false;
|
|
}
|
|
|
|
if (!m_dialogGeometry)
|
|
{
|
|
if (m_isAudio)
|
|
m_appHandle = PlaybackAppHandle(app);
|
|
else
|
|
m_appHandle = PlaybackAppHandle(app, QRect(0, 0, 0, 0));
|
|
}
|
|
else
|
|
m_appHandle = PlaybackAppHandle(app, *m_dialogGeometry);
|
|
|
|
if (!m_appHandle)
|
|
{
|
|
ERROR_LOG(m_logger) << "appHandle: missing app after creating appHandle!";
|
|
return false;
|
|
}
|
|
|
|
NGL::Settings s({});
|
|
bool isLocal = s.isDatabaseConnectionLocal();
|
|
|
|
DEBUG_LOG(m_logger) << "isLocal? " << isLocal;
|
|
|
|
AvaScriptSource source;
|
|
if (isLocal)
|
|
{
|
|
if (m_isAudio)
|
|
source = AvaScriptSource::LocalAudio;
|
|
else
|
|
source = AvaScriptSource::LocalVideo;
|
|
}
|
|
else
|
|
{
|
|
if (m_isAudio)
|
|
source = AvaScriptSource::ServerAudio;
|
|
else
|
|
source = AvaScriptSource::ServerVideo;
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << "source: " << static_cast<int>(source);
|
|
|
|
ctrl->setScript(m_appHandle.dialogId(), m_scriptName, source, topicData.streamList(),
|
|
LtsUtils::createConnection(ctrl, &AudioVideoManagement::AvaControl::setScriptResponse, this,
|
|
&TopicReplay::slotSetScriptResponse));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TopicReplay::playbackStart(int secondsOffset)
|
|
{
|
|
DEBUG_LOG(m_logger) << "TopicReplay::playbackStart()";
|
|
|
|
if (m_screenPlacementHelper && !m_isAudio)
|
|
m_screenPlacementHelper->show();
|
|
|
|
AvaControl* ctrl = getAvaControl();
|
|
if (ctrl == nullptr)
|
|
{
|
|
ERROR_LOG(m_logger) << "ctrl == nullptr";
|
|
return false;
|
|
}
|
|
|
|
AvaTimePoint p{AvaDuration{std::chrono::seconds(secondsOffset)}};
|
|
|
|
if (!m_appHandle)
|
|
{
|
|
ERROR_LOG(m_logger) << "appHandle: missing app!";
|
|
return false;
|
|
}
|
|
|
|
ctrl->setAbsPositionScript(m_appHandle.dialogId(), m_scriptName, p);
|
|
ctrl->startScript(m_appHandle.dialogId(), m_scriptName);
|
|
|
|
m_isPlaying = true;
|
|
DEBUG_LOG(m_logger) << "m_isPlaying = true";
|
|
return true;
|
|
}
|
|
|
|
bool TopicReplay::playbackPause()
|
|
{
|
|
DEBUG_LOG(m_logger) << "TopicReplay::playbackPause()";
|
|
|
|
if (m_screenPlacementHelper)
|
|
m_screenPlacementHelper->hide();
|
|
|
|
if (!m_isPlaying)
|
|
return true;
|
|
|
|
AvaControl* ctrl = getAvaControl();
|
|
if (ctrl == nullptr)
|
|
{
|
|
ERROR_LOG(m_logger) << "no ctrl!";
|
|
return false;
|
|
}
|
|
|
|
if (!m_appHandle)
|
|
{
|
|
ERROR_LOG(m_logger) << "appHandle: missing app!";
|
|
return false;
|
|
}
|
|
|
|
ctrl->pauseScript(m_appHandle.dialogId(), m_scriptName);
|
|
return true;
|
|
}
|
|
|
|
bool TopicReplay::playbackStop()
|
|
{
|
|
DEBUG_LOG(m_logger) << "TopicReplay::playbackStop()";
|
|
|
|
if (m_screenPlacementHelper)
|
|
m_screenPlacementHelper->hide();
|
|
|
|
if (!m_isPlaying)
|
|
return true;
|
|
|
|
AvaControl* ctrl = getAvaControl();
|
|
if (ctrl == nullptr)
|
|
{
|
|
ERROR_LOG(m_logger) << "no ctrl!";
|
|
return false;
|
|
}
|
|
|
|
if (!m_appHandle)
|
|
{
|
|
ERROR_LOG(m_logger) << "appHandle: missing app!";
|
|
return false;
|
|
}
|
|
|
|
ctrl->stopScript(m_appHandle.dialogId(), m_scriptName);
|
|
ctrl->releaseScript(m_appHandle.dialogId(), m_scriptName);
|
|
|
|
m_isPlaying = false;
|
|
return true;
|
|
}
|
|
|
|
bool TopicReplay::exportTopic(const QJsonObject& attributes, const QString& folderPath,
|
|
TopicEditorBase::ExportNotifier* exportNotifier)
|
|
{
|
|
Q_ASSERT(!m_exportScript);
|
|
|
|
VdControl* ctrl = getVdControl();
|
|
if (ctrl == nullptr)
|
|
{
|
|
ERROR_LOG(m_logger) << "ctrl missing!";
|
|
exportNotifier->setExportFailed();
|
|
return false;
|
|
}
|
|
|
|
m_exportScript.reset(new ExportScript(folderPath, exportNotifier));
|
|
|
|
AvaTopicData topicData(attributes);
|
|
m_scriptName = topicData.scriptName();
|
|
|
|
if (!topicData.isValid)
|
|
{
|
|
ERROR_LOG(m_logger) << "topicData is invalid";
|
|
exportNotifier->setExportFailed();
|
|
return false;
|
|
}
|
|
|
|
QFileInfo fileInfo(QDir(folderPath).filePath(topicData.scriptName()));
|
|
|
|
Q_ASSERT(topicData.isAudio() == m_isAudio);
|
|
|
|
ctrl->vdSetScript(m_scriptName, m_isAudio ? AvaStreamType::Audio : AvaStreamType::Video, 0, topicData.streamList(),
|
|
fileInfo.baseName(), fileInfo.absolutePath(), this, &TopicReplay::avScriptFinished);
|
|
return true;
|
|
}
|
|
|
|
void TopicReplay::onWidgetPlaced(const QRect& geometry)
|
|
{
|
|
WARNING_LOG(m_logger) << "TopicReplay::onWidgetPlaced() - geo: " << geometry.x() << "/" << geometry.y()
|
|
<< " m_isAudio=" << m_isAudio;
|
|
|
|
m_dialogGeometry = geometry;
|
|
|
|
if (m_isAudio)
|
|
return;
|
|
|
|
PlaybackApp* app = m_isAudio ? m_dataIfc->audioPresentationApp() : m_dataIfc->videoPresentationApp();
|
|
if (app == nullptr)
|
|
{
|
|
ERROR_LOG(m_logger) << "app missing!";
|
|
return;
|
|
}
|
|
|
|
if (!app->inUse())
|
|
{
|
|
ERROR_LOG(m_logger) << "app not in use - waiting for playbackInit to aquire it";
|
|
return;
|
|
}
|
|
|
|
DEBUG_LOG(m_logger) << "setGeometry";
|
|
app->setGeometry(*m_dialogGeometry);
|
|
}
|
|
|
|
AvaControl* TopicReplay::getAvaControl()
|
|
{
|
|
if (!m_dataIfc->hasAvsSession())
|
|
return nullptr;
|
|
|
|
AvaSession* avaSession = m_dataIfc->avaSession();
|
|
if (!avaSession->isEstablished())
|
|
return nullptr;
|
|
|
|
return avaSession->control();
|
|
}
|
|
|
|
VdControl* TopicReplay::getVdControl()
|
|
{
|
|
if (!m_dataIfc->hasAvsSession())
|
|
return nullptr;
|
|
|
|
VdSession* vdSession = m_dataIfc->vdSession();
|
|
if (!vdSession->isEstablished())
|
|
return nullptr;
|
|
|
|
return vdSession->control();
|
|
}
|
|
|
|
void TopicReplay::avScriptFinished(const QString& exportDir, AvaStreamType streamType, bool success)
|
|
{
|
|
VdControl* ctrl = getVdControl();
|
|
ctrl->vdReleaseScript(m_scriptName);
|
|
m_exportScript->avScriptFinished(exportDir, streamType, success);
|
|
}
|
|
|
|
TopicReplay::ExportScript::ExportScript(const QString& topLevelExportDir, TopicEditorBase::ExportNotifier* exportStep)
|
|
: m_topLevelExportDir(topLevelExportDir), m_exportStep(exportStep)
|
|
{
|
|
connect(exportStep, &QObject::destroyed, this, [this]() {
|
|
if (static_cast<TopicEditorBase::ExportNotifier*>(sender()) != m_exportStep)
|
|
return;
|
|
m_exportStep = nullptr;
|
|
});
|
|
Q_ASSERT(exportStep);
|
|
}
|
|
|
|
void TopicReplay::ExportScript::avScriptFinished(const QString& exportDir, AvaStreamType streamType, bool success)
|
|
{
|
|
QString absPathToPlaylist = audioVideoPlaylistErstellen(exportDir, streamType);
|
|
|
|
QDir toplevelExportDir(m_topLevelExportDir);
|
|
QString relativePathToPlaylist = toplevelExportDir.relativeFilePath(absPathToPlaylist);
|
|
|
|
if (m_exportStep == nullptr)
|
|
return;
|
|
|
|
if (success)
|
|
m_exportStep->setExportSuccessful(relativePathToPlaylist);
|
|
else
|
|
m_exportStep->setExportFailed();
|
|
}
|
|
|
|
QString TopicReplay::ExportScript::audioVideoPlaylistErstellen(const QString& dir, AvaStreamType streamType)
|
|
{
|
|
QDir avDir(dir);
|
|
const QFileInfoList dateienList = avDir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
|
|
|
|
QByteArray dataForFile;
|
|
QString data;
|
|
dataForFile.append("[playlist]\n");
|
|
|
|
int numberOfEntries = 0;
|
|
int i = 1;
|
|
const QString tmpl1(QStringLiteral("File %1=%2\n"));
|
|
const QString tmpl2(QStringLiteral("# File %1=%2\n"));
|
|
for (const QFileInfo& fileInfo : dateienList)
|
|
{
|
|
if (streamType == AvaStreamType::Video && fileInfo.suffix() == QLatin1String(".wav"))
|
|
{
|
|
data += tmpl2.arg(QString::number(i), fileInfo.fileName());
|
|
i--;
|
|
}
|
|
else
|
|
{
|
|
data += tmpl1.arg(QString::number(i), fileInfo.fileName());
|
|
numberOfEntries++;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
QString numberOfEntriesString =
|
|
QStringLiteral("NumberOfEntries=") + QString::number(numberOfEntries) + QStringLiteral("\n");
|
|
dataForFile.append(numberOfEntriesString.toUtf8());
|
|
dataForFile.append(data.toUtf8());
|
|
|
|
QString fileName;
|
|
switch (streamType)
|
|
{
|
|
case AvaStreamType::Audio:
|
|
fileName = avDir.filePath(QStringLiteral("audio.pls"));
|
|
break;
|
|
|
|
case AvaStreamType::Video:
|
|
fileName = avDir.filePath(QStringLiteral("video.pls"));
|
|
break;
|
|
}
|
|
|
|
writeFile(dataForFile, fileName);
|
|
return fileName;
|
|
}
|
|
|
|
bool TopicReplay::ExportScript::writeFile(const QByteArray& data, const QString& fileName)
|
|
{
|
|
QFile file(fileName);
|
|
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
|
return false;
|
|
|
|
if (file.write(data) == -1)
|
|
{
|
|
file.close();
|
|
return false;
|
|
}
|
|
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace AudioVideoManagement
|
|
|
|
#include "topic_replay.moc"
|