/** * @RDE_HEADER@ * AvaComm * $Id$ * $HeadURL$ * $Author$ * Created by Christian Fröbel, SLSE, 2017-06-16 */ #include "ava_comm.h" #include "av_helper.h" #include #include #include #include #include #include #include #include #include namespace AudioVideoManagement { AvaComm::AvaComm(const QString &avaHost, quint16 avaPort) : m_avSocket(avaHost, avaPort, getLogger("AvaComm"), getLogger("AvaComm.IO")) , m_logger(getLogger("AvaComm")) , m_loggerIO(getLogger("AvaComm.IO")) { connect(&m_avSocket, &AVSocketWrapper::readyRead, this, &AvaComm::onReadyRead); connect(&m_avSocket, &AVSocketWrapper::disconnected, this, &AvaComm::disconnected); // signal -> signal } void AvaComm::start() { m_avSocket.connectToAVS(); } void AvaComm::forceDisconnect() { m_avSocket.disconnectFromHost(); } void AvaComm::switchContext(int exerciseId) { m_avSocket.send(QByteArray("switchContext ex_").append(QByteArray::number(exerciseId))); } void AvaComm::setUserProfile(const QString &userName, const QString &langCode, AvaAudioPrio audioPrio, AvaGuiStyle style) { static const QString cmd = QStringLiteral(R"(setUserProfile "%1" %2 %3 %3 "" "" %4)"); m_avSocket.send(cmd.arg(userName, langCode).arg(static_cast(audioPrio)).arg(static_cast(style)).toUtf8()); } void AvaComm::setTimezone(QChar tz) { char timezone = tz.toLatin1(); if (!DTG::isTimezoneValid(timezone)) { timezone = 'Z'; Q_ASSERT(false); } const int ofs = DTG::timezone2Secs(timezone); static const QString cmd = QStringLiteral("setTimezone %1 %2"); m_avSocket.send(cmd.arg(tz).arg(ofs).toUtf8()); } void AvaComm::setVideoDialogProfile(const QString &dialogId, AvaVisibility visibility) { static const QString cmd = QStringLiteral(R"(setVideoDialogProfile "%1" * * * * * * * * * * * * * * * %2 * * * *)"); m_avSocket.send(cmd.arg(dialogId).arg(static_cast(visibility)).toUtf8()); } void AvaComm::setVideoDialogGeometry(const QString &dialogId, const QRect &geometry) { static const QString cmd = QStringLiteral(R"(setVideoDialogProfile "%1" * %2 %3 %4 %5 * * * * * * * * * * %6 * * * *)"); m_avSocket.send(cmd.arg(dialogId).arg(geometry.x()).arg(geometry.y()).arg(geometry.width()).arg(geometry.height()).arg(static_cast(AvaVisibility::Visible)).toUtf8()); } void AvaComm::setVideoProfile(const QString &dialogId, const QString &menuDef) { static const QString cmd = QStringLiteral(R"(setVideoProfile "%1" "%2")"); m_avSocket.send(cmd.arg(dialogId, menuDef).toUtf8()); } void AvaComm::setAnnouncementProfile(AvaSumChannel sumChannel, const QString &menuDef, const QString &identityList, const QString &stateList) { static const QString cmd = QStringLiteral(R"(SetAnnouncementProfile "%1" "%2" "%3" %4)"); m_avSocket.send(cmd.arg(menuDef, identityList, stateList, QString::number(static_cast(sumChannel))).toUtf8()); } void AvaComm::getStreamList(AvaStreamType streamType, AvaStreamSource streamSource) { static const QString cmd = QStringLiteral("getStreamList %1 %2"); m_avSocket.send(cmd.arg(static_cast(streamType)).arg(static_cast(streamSource)).toUtf8()); } void AvaComm::chooseStream(const QString &dialogId, const QString &streamName, bool muted) { static const QString cmd = QStringLiteral(R"(chooseStream "%1" %2 "%3")"); m_avSocket.send(cmd.arg(streamName).arg(muted).arg(dialogId).toUtf8()); } void AvaComm::chooseStreamOverview(const QString &dialogId, const QStringList &streamNames, bool muted) { static const QString cmd = QStringLiteral(R"(chooseStreamOverview "%1" %2 "%3")"); Q_ASSERT(!streamNames.empty()); QString streamArgs; QTextStream s(&streamArgs, QIODevice::WriteOnly); s << QLatin1Char('{') << streamNames.first() << QLatin1Char('}'); for (int i = 1; i < streamNames.size(); ++i) s << QStringLiteral(" {") << streamNames.at(i) << QLatin1Char('}'); m_avSocket.send(cmd.arg(streamArgs).arg(muted).arg(dialogId).toUtf8()); } void AvaComm::releaseStream(const QString &dialogId, const QString &streamName) { static const QString cmd = QStringLiteral(R"(releaseStream "%1" "%2")"); m_avSocket.send(cmd.arg(streamName, dialogId).toUtf8()); } //void AvaComm::setAbsPosition(const QString &dialogId, const QString &streamName, AvaTimePoint pos) //{ // static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" posa %3)"); // m_avSocket.send(cmd.arg(streamName, dialogId, toString(pos)).toUtf8()); //} void AvaComm::queryTimeline(const QString &streamName, const boost::optional &start, const boost::optional &end) { static const QString cmd = QStringLiteral(R"(queryTimeline "%1" 1)"); // 1 = get list QString finalCmd(cmd.arg(streamName)); if (start && end) finalCmd += QStringLiteral(" %1 %2").arg(toString(start.get()), toString(end.get())); m_avSocket.send(finalCmd.toUtf8()); } void AvaComm::setVolume(AvaVolume db) { static const QString cmd = QStringLiteral("setVolume 1 %1"); m_avSocket.send(cmd.arg(db).toUtf8()); } void AvaComm::startStream(const QString &dialogId, const QString &streamName, AvaSpeed speed) { static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" start %3)"); m_avSocket.send(cmd.arg(streamName, dialogId, QString::number(speed)).toUtf8()); } void AvaComm::stopStream(const QString &dialogId, const QString &streamName) { static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" stop)"); m_avSocket.send(cmd.arg(streamName, dialogId).toUtf8()); } void AvaComm::pauseStream(const QString &dialogId, const QString &streamName) { static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" pause)"); m_avSocket.send(cmd.arg(streamName, dialogId).toUtf8()); } void AvaComm::setStreamSpeed(const QString &dialogId, const QString &streamName, AvaSpeed speed) { static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" sped %3)"); m_avSocket.send(cmd.arg(streamName, dialogId, QString::number(speed)).toUtf8()); } void AvaComm::setAbsPosition(const QString &dialogId, const QString &streamName, AvaTimePoint pos) { static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" posa %3)"); m_avSocket.send(cmd.arg(streamName, dialogId, toString(pos)).toUtf8()); } void AvaComm::positionUpdate(const QString &dialogId, const QString &streamName, std::chrono::milliseconds updateInterval) { Q_ASSERT(updateInterval <= std::chrono::seconds(1)); static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" posu %3)"); m_avSocket.send(cmd.arg(streamName, dialogId, QString::number(updateInterval.count())).toUtf8()); } void AvaComm::setPint(const QString &dialogId, const QString &streamName, bool doSkip) { static const QString cmd = QStringLiteral(R"(streamControl "%1" "%2" pint %3)"); m_avSocket.send(cmd.arg(streamName, dialogId).arg(!doSkip).toUtf8()); } void AvaComm::getElokaList(const QString &streamName) { static const QString cmd = QStringLiteral(R"(getElokaList "%1")"); m_avSocket.send(cmd.arg(streamName).toUtf8()); } void AvaComm::chooseEloka(const QString &streamName, const QString &signalName, AvaElokaMode mode) { static const QString cmd = QStringLiteral(R"(chooseEloka "%1" "%2" %3)"); m_avSocket.send(cmd.arg(signalName, streamName).arg(static_cast(mode)).toUtf8()); } void AvaComm::releaseEloka(const QString &streamName) { static const QString cmd = QStringLiteral(R"(releaseEloka "%1")"); m_avSocket.send(cmd.arg(streamName).toUtf8()); } void AvaComm::queryEloka(AvaQueryElokaMode mode) { static const QString cmd = QStringLiteral(R"(queryEloka "%1")"); m_avSocket.send(cmd.arg(static_cast(mode)).toUtf8()); } void AvaComm::defineStreamNameMapping(const QString &internalName, const QString &displayName) { static const QString cmd = QStringLiteral(R"(defineStreamNameMapping AUDIO_VIDEO "{%1} {%2}")"); m_avSocket.send(cmd.arg(internalName, displayName).toUtf8()); } void AvaComm::resetStreamNameMapping() { static const QString cmd = QStringLiteral("resetStreamNameMapping AUDIO_VIDEO"); m_avSocket.send(cmd.toUtf8()); } void AvaComm::keepAlive() { static const QString cmd = QStringLiteral("KeepAlive"); m_avSocket.send(cmd.toUtf8()); } void AvaComm::chooseImport(const QString &dialogId, const QString &path, int mode) { static const QString cmd = QStringLiteral(R"(chooseImport "%1" "%2" %3)"); m_avSocket.send(cmd.arg(dialogId, path, QString::number(mode)).toUtf8()); } void AvaComm::releaseImport(const QString &dialogId) { static const QString cmd = QStringLiteral(R"(releaseImport "%1")"); m_avSocket.send(cmd.arg(dialogId).toUtf8()); } void AvaComm::setScript(const QString &dialogId, const QString &scriptName, AvaScriptSource source, const QVector &streams) { QStringList streamList; streamList.reserve(streams.size()); for (const auto &stream : streams) { /*/ * NOTE: * Clemens Fricke on 11 Dec 2020: * Subject: AvaScriptSource * * In lokalen Skripten muss der vollständige Name angegeben werden, der beim Befehl GetStreamList zurückgegeben wird. * Wenn der StreamName nicht mit einer Extension endet, wird “(S)“ als Default angenommen. * Nehmen wir den Fall für den Download: * Die Situation an der RCP_AVA-Schnittstelle ist wie folgt: * * >GetStreamList 2 2 * <293 STREAMLIST * <2 2 "2610 Video Test(S)” * <299 ENDOFPARAMETERLIST * * Im Download Skript sollte dann der Name „2610 Video Test(S)“ verwendet werden: * SetScript "Download" 2 "LOCAL_RECORD" “{2610 Video Test(S)} 100 0 1603700404.00 1603700415.00 1 0.1\n{2610 Video Test(S)} 100 0 1603700418.00 1603700431.00 1 1\n{2610 Video Test(S)} 100 0 1603700412.00 1603700418.00 1 1” * Wenn sie nach dem Download die globale und lokale Stream-Liste abfragen ergibt sich folgende Situation: * * >GetStreamList 2 2 * <293 STREAMLIST * <2 2 "2610 Video Test(S)” * <299 ENDOFPARAMETERLIST * * >GetStreamList 2 4 * <293 STREAMLIST * <2 2 "2610 Video Test(L)” * <299 ENDOFPARAMETERLIST * * Im System gibt es einen Stream mit der Extension (S) vom globalen Server und einen Stream mit der Extension (L) vom privaten lokalen Server. * Wenn Sie jetzt ein Skript ausführen wollen, das ausschließlich die lokale Kopie verwenden soll, dann müssen sie im Skript entsprechend für alle * Einträge die Extension (L) angeben. * z.B. SetScript "Download" 4 "Videodialog 1" "{2610 Video Test(L)} 100 0 1607011182.20 1607011334.73 0\n{2610 Video Test(L)} 100 0 1607013012.64 1607013184.24 0" * Ich habe im Source-Code nachgesehen, der zweite Parameter ist ein Relikt aus der vorherigen System-Inkarnation. Er wird nicht mehr ausgewertet. * Die Auswahl zwischen lokal und global wird ausschließlich über die Extension der Stream-Namen innerhalb des Skriptes vorgenommen. * * tl;dr AvaScriptSource deprecated for setScript, add (L) for local or (S) for Server to stream name * * AvaScriptSource will be used to determine the script source to append to the stream name. * For compatibility issues, AvaScriptSource is kept as parameter for the method signature, but internally AvaScriptSource will be converted * to the needed parameters for the RPC call. /*/ QString sourceString; switch (source) { case AvaScriptSource::LocalAudio: case AvaScriptSource::LocalVideo: { sourceString = QStringLiteral("L"); break; } case AvaScriptSource::ServerAudio: case AvaScriptSource::ServerVideo: { sourceString = QStringLiteral("S"); break; } } // not in db here! const double vol = pow(10.0, double(stream.db) / 20.0); streamList += QStringLiteral("{%1(%8)} %2 %3 %4 %5 %6 %7") .arg(stream.name, QString::number(stream.speed), QString::number(stream.mute), toString(stream.start), toString(stream.stop), QString::number(!stream.pint), QString::number(vol), sourceString); } static const QString cmd = QStringLiteral(R"(setScript "%1" %2 "%3" "%4")"); m_avSocket.send(cmd.arg(scriptName).arg((source == AvaScriptSource::LocalAudio || source == AvaScriptSource::ServerAudio) ? 1 : 2) /* ignore source, see comment above for the why */ .arg(dialogId, streamList.join(QLatin1Char('\n'))).toUtf8()); } void AvaComm::releaseScript(const QString &dialogId, const QString &scriptName) { static const QString cmd = QStringLiteral(R"(releaseScript "%1" "%2")"); m_avSocket.send(cmd.arg(scriptName, dialogId).toUtf8()); } void AvaComm::sendPreparedCommand(const QString &command) { m_avSocket.send(command.toUtf8()); } void AvaComm::startScript(const QString &dialogId, const QString &scriptName) { static const QString cmd = QStringLiteral(R"(scriptControl "%1" "%2" start 1000)"); m_avSocket.send(cmd.arg(scriptName, dialogId).toUtf8()); } void AvaComm::stopScript(const QString &dialogId, const QString &scriptName) { static const QString cmd = QStringLiteral(R"(scriptControl "%1" "%2" stop)"); m_avSocket.send(cmd.arg(scriptName, dialogId).toUtf8()); } void AvaComm::pauseScript(const QString &dialogId, const QString &scriptName) { static const QString cmd = QStringLiteral(R"(scriptControl "%1" "%2" pause)"); m_avSocket.send(cmd.arg(scriptName, dialogId).toUtf8()); } void AvaComm::setAbsPositionScript(const QString &dialogId, const QString &scriptName, AvaTimePoint pos) { static const QString cmd = QStringLiteral(R"(scriptControl "%1" "%2" posa %3)"); m_avSocket.send(cmd.arg(scriptName, dialogId, toString(pos)).toUtf8()); } void AvaComm::onReadyRead(QTcpSocket *socket) { Q_ASSERT(socket); while (socket->canReadLine()) parseResponse(socket->readLine().trimmed()); } void AvaComm::parseResponse(const QByteArray &line) { static const QByteArray GREETER("RCP_AVA/1.0"); if (line.isEmpty()) return; DEBUG_LOG(m_loggerIO) << "From A/V: '" << line << "'"; if (line == GREETER) { emit connected(); return; } const auto parts = AVSocket::parseLine(line); if (m_pendingList) { parseListValue(parts); return; } bool couldConvert = false; Response response = static_cast(parts.at(0).toInt(&couldConvert)); Q_ASSERT(couldConvert); switch (response) { case Response::Ok: Q_ASSERT(parts.size() == 2); emit ok(); break; case Response::RtChoiceUnitState: { Q_ASSERT(parts.size() == 5); AvaRtPos pos = parts.at(1).toInt(&couldConvert); Q_ASSERT(couldConvert); QString streamName = QString::fromUtf8(parts.at(2)); AvaRtButtonState buttonState = static_cast(parts.at(3).toInt(&couldConvert)); Q_ASSERT(couldConvert); emit rtChoiceUnitStateReceived(pos, streamName, buttonState); break; } case Response::SumChannelState: { Q_ASSERT(parts.size() == 3); AvaSumChannel sumChannel = static_cast(parts.at(1).toInt(&couldConvert)); Q_ASSERT(couldConvert); emit sumChannelStateReceived(sumChannel); break; } case Response::Volume: { Q_ASSERT(parts.size() == 4); int device = parts.at(1).toInt(&couldConvert); Q_ASSERT(couldConvert); Q_ASSERT(device == 1); AvaVolume db = parts.at(2).toInt(&couldConvert); Q_ASSERT(couldConvert); emit volumeReceived(db); break; } case Response::VideoDialogState: Q_ASSERT(parts.size() == 4); break; case Response::KeepAlive: emit keepAliveReceived(); break; case Response::StreamInfo: { Q_ASSERT(parts.size() == 7); QString streamName = QString::fromUtf8(parts.at(1)); QString dialogId = QString::fromUtf8(parts.at(2)); AvaTimePoint start = createTimePoint(parts.at(3)); AvaTimePoint end = createTimePoint(parts.at(4)); AvaDuration duration = createDuration(parts.at(5)); emit streamInfoReceived(dialogId, streamName, AvaStreamInfo{start, end, duration}); break; } case Response::StreamPos: { Q_ASSERT(parts.size() == 5); QString streamName = QString::fromUtf8(parts.at(1)); QString dialogId = QString::fromUtf8(parts.at(2)); AvaTimePoint pos = createTimePoint(parts.at(3)); emit streamPositionReceived(dialogId, streamName, pos); break; } case Response::StreamSpeed: { Q_ASSERT(parts.size() == 5); QString streamName = QString::fromUtf8(parts.at(1)); QString dialogId = QString::fromUtf8(parts.at(2)); AvaSpeed speed(parts.at(3).toInt(&couldConvert)); Q_ASSERT(couldConvert); emit streamSpeedReceived(dialogId, streamName, speed); break; } case Response::StreamState: { Q_ASSERT(parts.size() == 5); QString streamName = QString::fromUtf8(parts.at(1)); QString dialogId = QString::fromUtf8(parts.at(2)); AvaStreamState streamState = static_cast(parts.at(3).toInt(&couldConvert)); Q_ASSERT(couldConvert); emit streamStateReceived(dialogId, streamName, streamState); break; } case Response::StreamMode: { Q_ASSERT(parts.size() == 6); QString streamName = QString::fromUtf8(parts.at(1)); QString dialogId = QString::fromUtf8(parts.at(2)); bool pint = parts.at(3).toInt(&couldConvert); Q_ASSERT(couldConvert); bool autoStop = parts.at(4).toInt(&couldConvert); Q_ASSERT(couldConvert); emit streamModeReceived(dialogId, streamName, pint, autoStop); break; } case Response::StreamAudioMute: Q_ASSERT(parts.size() == 5); break; case Response::StreamAudioState: Q_ASSERT(parts.size() == 5); break; case Response::ElokaState: Q_ASSERT(parts.size() == 5); break; case Response::ElokaList: Q_ASSERT(parts.size() == 2); Q_ASSERT(!m_pendingList); m_pendingList = Response::ElokaList; emit elokaListBeginReceived(); break; case Response::StreamVolumeChanged: Q_ASSERT(parts.size() == 5); break; case Response::RtVolumeUser: { Q_ASSERT(parts.size() == 4); QString streamName = QString::fromUtf8(parts.at(1)); AvaVolume db = parts.at(2).toInt(&couldConvert); Q_ASSERT(couldConvert); emit rtVolumeUserReceived(streamName, db); break; } case Response::InvalidNameList: Q_ASSERT(parts.size() == 2); Q_ASSERT(!m_pendingList); m_pendingList = Response::InvalidNameList; emit invalidNameListBeginReceived(); break; case Response::StreamList: Q_ASSERT(parts.size() == 2); Q_ASSERT(!m_pendingList); m_pendingList = Response::StreamList; emit streamListBeginReceived(); break; case Response::QueryElokaList: Q_ASSERT(parts.size() == 2); Q_ASSERT(!m_pendingList); m_pendingList = Response::QueryElokaList; emit queryElokaListBeginReceived(); break; case Response::TimelineList: { Q_ASSERT(parts.size() == 3); Q_ASSERT(!m_pendingList); m_pendingList = Response::TimelineList; QString streamName = QString::fromUtf8(parts.at(1)); emit timelineListBeginReceived(streamName); break; } case Response::ScriptInfo: { Q_ASSERT(parts.size() == 7); QString scriptName = QString::fromUtf8(parts.at(1)); QString dialogId = QString::fromUtf8(parts.at(2)); AvaTimePoint start = createTimePoint(parts.at(3)); AvaTimePoint end = createTimePoint(parts.at(4)); AvaDuration duration = createDuration(parts.at(5)); emit scriptInfoReceived(dialogId, scriptName, AvaStreamInfo{start, end, duration}); break; } case Response::ScriptState: { Q_ASSERT(parts.size() == 5); QString scriptName = QString::fromUtf8(parts.at(1)); QString dialogId = QString::fromUtf8(parts.at(2)); AvaStreamState scriptState = static_cast(parts.at(3).toInt(&couldConvert)); Q_ASSERT(couldConvert); emit scriptStateReceived(dialogId, scriptName, scriptState); break; } case Response::ScriptPos: { Q_ASSERT(parts.size() == 7); QString scriptName = QString::fromUtf8(parts.at(1)); QString dialogId = QString::fromUtf8(parts.at(2)); QString streamName = QString::fromUtf8(parts.at(3)); AvaTimePoint pos = createTimePoint(parts.at(4)); AvaTimePoint relPos = createTimePoint(parts.at(5)); NOTICE_LOG(m_logger) << "Script pos received: " << scriptName << ", " << dialogId; emit scriptPositionReceived(dialogId, scriptName, streamName, pos, relPos); break; } default: DEBUG_LOG(m_logger) << "Response not implemented: " << line; } } void AvaComm::parseListValue(const QVector &parts) { Q_ASSERT(m_pendingList); bool couldConvert = false; Response response = static_cast(parts.at(0).toInt(&couldConvert)); if (couldConvert && response == Response::EndOfParameterList) { m_pendingList.reset(); emit endOfListReceived(); return; } emit listValueReceived(parts); } } // namespace AudioVideoManagement