/** * @RDE_HEADER@ * TopicReplay * $Id$ * $HeadURL$ * $Author$ * Created by Christian Fröbel, NLSE, 2018-06-26 */ #include "topic_replay.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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) , m_logger(getLogger("TopicReplay")) { 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(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; // test avs before using it after the 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; } /* init anyway as the geometry changes whenever.. * if (!m_isAudio && !m_dialogGeometry) { //ERROR_LOG(m_logger) << "is video topic but no screen allocated for playback!"; ERROR_LOG(m_logger) << "is video topic but no screen allocated for playback!"; return false; // video topic but no screen allocated for playback }*/ if (!m_dialogGeometry) { if (m_isAudio) m_appHandle = PlaybackAppHandle(app); // don't give geo for audio, otherwise avs is advised to create a window for audio, but a window is not needed for audio else m_appHandle = PlaybackAppHandle(app, QRect(0, 0, 0, 0)); // prevent the PlaybackApp to ask MMM for a selection by providing an invalid geo (puts the window at the last known position) } 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 // Server { if (m_isAudio) source = AvaScriptSource::ServerAudio; else source = AvaScriptSource::ServerVideo; } DEBUG_LOG(m_logger) << "source: " << static_cast(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) { /*DEBUG_LOG*/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()) // playbackInit needs to create an AppHandle before we can use it, otherwise we would aquire the app at this point an playbackInit has no chance to work with the app because we aquired it here (NOTE: aquiring an app blocks it from being modified, etc) { 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(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"